Script Not Inheriting Environment Variables
I have a script that normally runs from a scheduler (at, cron). For testing I run the script manually from a terminal window.
The script is declared as #!/bin/sh. When I run the script manually from the command line, the script does not inherit the $PS1 or $- environment variables. When I change the declaration to #!/bin/bash then the script inherits the $PS1 variable but not the $-. The script is rather lengthy and when I run from a terminal I want certain echo messages to appear to help me with troubleshooting and improving the script. I wanted to key off the $PS1 or $- variables to determine whether the script is interactive or non-interactive. Yet this plan does not seem to work. I can manually run the script as interactive by prefixing sh -i before the script name. I then see my special test messages. The script is not interactive in the sense that I need to respond to anything from the script. The script is interactive only in the sense that I want to see my special messages when run manually from the command line and I don't want to see these message when run from the schedulers. Why does the script have to be declared as /bin/bash to see $PS1 and what can I do to inherit that variable and the $- variable? The variables are present in the terminal window. I thought child processes are supposed to inherit the environment variables of the parent process. Thanks again. |
Quote:
Quote:
|
Scripts will not inherit variables that are not export'd
|
Quote:
Quote:
Quote:
I can get $PS1 in the script's environment only if I change the script declaration to /bin/bash rather than /bin/sh. Why does the declaration of /bin/sh ignore the $PS1 variable and /bin/bash does not? The cause would seem to be with the shell interpreter? Note: On my systems /bin/sh is sym linked to /bin/bash. |
EDIT: nevermind, i'm an idiot
|
"set" prints both your environment variables and your shell variables, "printenv" only prints the environment variables.
Actually, for your purposes, it might be more appropriate to key off of whether or no stdout is a terminal, by using [ -t 1 ] or [[ -t 1 ]] |
Quote:
Quote:
Quote:
Quote:
|
Thanks for the responses. :D
Seems the detection of $PS1 is controlled by the shell interpreter. The /etc/file/magic configuration file plays a role. The shell interpreter uses that file to determine the shell's respective behavior. In that file the !/bin/sh declaration is interpreted as a Bourne shell. The !/bin/bash declaration is interpreted as a Bourne-Again shell. The Bourne-Again shell will detect the $PS1 environment variable and the Bourne shell will not. From the Advanced Bash-Scripting Guide, Chapter 2: The sha-bang ( #!) [1] at the head of a script tells your system that this file is a set of commands to be fed to the command interpreter indicated. The #! is actually a two-byte [2] magic number, a special marker that designates a file type, or in this case an executable shell script (type man magic for more details on this fascinating topic). Immediately following the sha-bang is a path name. This is the path to the program that interprets the commands in the script, whether it be a shell, a programming language, or a utility. This command interpreter then executes the commands in the script, starting at the top (the line following the sha-bang line), and ignoring comments. [3] From the Bash Reference Manual, 6.2 Bash Startup Files: Since a shell invoked as sh does not attempt to read and execute commands from any other startup files . . . This would indicate that despite the sym link between /bin/sh and /bin/bash, when invoked using the /bin/sh declaration, bash will not read and execute the bash startup files. That includes /etc/profile, etc/bashrc, ~/.bashrc, etc. Hence the $PS1 environment variable is unavailable when a script is invoked with !/bin/sh. From the Advanced Bash-Scripting Guide,, Chapter 31: Using Bash-specific functionality in a Bourne shell script (#!/bin/sh) on a non-Linux machine may cause unexpected behavior. A Linux system usually aliases sh to bash, but this does not necessarily hold true for a generic UNIX machine. A shell script headed by #!/bin/sh will not run in full Bash-compatibility mode. Some Bash-specific functions might be disabled. Scripts that need complete access to all the Bash-specific extensions should start with #!/bin/bash. The detection of the $PS1 variable is a Bourne-Again shell feature and is unavailable in the generic Bourne shell. From the Advanced Bash-Scripting Guide,, Chapter 33-10: Note that /bin/sh is a link to /bin/bash in Linux and certain other flavors of UNIX, and a script invoked this way disables extended Bash functionality. Perhaps then the question is not how to detect $PS1, which is a Bourne-Again shell feature, but how to detect or invoke interactive mode in a portable manner across both the Bourne and Bourne-Again shells, or any shell. One trick for better portability would seem to be to use the /usr/bin/env declaration, but that trick basically is the same as using the !/bin/bash declaration. The !/bin/sh declaration ensures better portability (POSIX compliance) between operating systems. Hence my use of the !/bin/sh declaration in my shell scripts. Which is why originally I tried to detect the contents of $- rather than $PS1. I must have run across a lot of this information long ago and that got lost in the cobwebs of my mind. :) So why doesn't $- work correctly? Once upon a time I read that the $- variable detection is supposed to work across various shells and be more portable. Apparently that information is incorrect. I'd like to know when the $- detection method works within a script. My script always detects the variable but never sees the variable as confirming interactive mode. When I start my script prefixed with the sh -i command, then the $- variable confirms interactive mode. For the immediate issue I can change my script declaration to !/bin/bash and obtain the dual effects (interactive versus non-interactive) I described in my original post. Yet I would like a more portable method for toggling between interactive and non-interactive mode when running a script directly from the command line. I suspect many people do not know about these subtle differences if they developed the habit of always using one declaration or the other. Feel free to add information to help better clarify these subtle quirks. |
Quote:
There must be a better way to display certain messages when run directly at the command line and to ignore those messages when run in the background from a scheduler. Any ideas? :scratch: Note: Hmm. Perhaps the $TERM variable will do the trick. When in a console the variable is set to linux and when in a terminal window the variable is set to xterm. When the script is run from the at daemon the variable is not part of the environment. The script declaration has no effect on the variable. Caveat: The at daemon assigns a value of dumb to the $TERM variable. The variable will not appear in the script's environment variable list, and a null value therefore cannot be used as a test. My script test now looks for a $TERM value of linux or xterm to determine whether I am running the script directly from the command line. |
Interesting about that /etc/file/magic. You know it's not just a file, at least not here on Slack 13, it's a directory with tons of files.
|
Quote:
Code:
#!/bin/sh |
Quote:
I tested the snippet. Works as advertised. :) Although operating differently, seems this approach and the method I tested in my previous post with the $TERM variable produce the same desired result with respect to me wanting to run scripts in either console/terminal or with the at daemon. Thanks everybody for an interesting discussion! :D |
It's an interesting discussion but I would rather see the diagnostic output as run from cron than from a terminal.
Scripts can run correctly in the terminal and not run correctly from cron. I have seen many times that the format of the output of a command used in a script can be different if run in the term or in cron. It's easy to suppress standard out while still having standard error mailed to you from a cron job, just end your crontab entry with > /dev/null Suppressing your output from the script itself when it is running in cron seems like extra work to do something that could be counterproductive and can easily be done with 12 characters in your crontab entry. Or so it seems to me. Russ |
All times are GMT -5. The time now is 11:37 PM. |