Bash Shell Scripting/Environment

Subshells
In Bash, one or more commands can be wrapped in parentheses, causing those commands to be executed in a "subshell". (There are also a few ways that subshells can be created implicitly; we will see those later.) A subshell receives a copy of the surrounding context's "execution environment", which includes any variables, among other things; but any changes that the subshell makes to the execution environment are not copied back when the subshell completes. So, for example, this script:

prints this: bar&#xA;bar&#xA;bip&#xA;foo&#xA;&#xA;bar

The same is true of function definitions; just like a regular variable, a function defined within a subshell is not visible outside the subshell.

A subshell also delimits changes to other aspects of the execution environment; in particular, the  ("change directory") command only affects the subshell. So, for example, this script:

prints this: /&#xA;/&#xA;/home&#xA;/

An  statement within a subshell terminates only that subshell. For example, this script:

prints this: subshell succeeded&#xA;subshell failed

Like in a script as a whole,  defaults to returning the exit status of the last-run command, and a subshell that does not have an explicit   statement will return the exit status of the last-run command.

Environment variables
We have already seen that, when a program is called, it receives a list of arguments that are explicitly listed on the command line. What we haven't mentioned is that it also receives a list of name-value pairs called "environment variables". Different programming languages offer different ways for a program to access an environment variable; C programs can use  (and/or accept them as a third argument to  ), Perl programs can use , Java programs can use  , and so forth.

In Bash, environment variables are simply made into regular Bash variables. So, for example, the following script prints out the value of the  environment variable:

The reverse, however, is not true: regular Bash variables are not automatically made into environment variables. So, for example, this script:

will not print, because the variable   is not passed into the   command as an environment variable. ( runs the one-line Bash script  .)

To turn a regular Bash variable into an environment variable, we have to "export" it into the environment. The following script does print :

Note that  doesn't just create an environment variable; it actually marks the Bash variable as an exported variable, and later assignments to the Bash variable will affect the environment variable as well. That effect is illustrated by this script:

The  command can also be used to remove a variable from an environment, by including the   option; for example,   undoes the effect of. And multiple variables can be exported or unexported in a single command, such as  or.

It's important to note that environment variables are only ever passed into a command; they are never received back from a command. In this respect, they are similar to regular Bash variables and subshells. So, for example, this command:

prints ; the change to   inside the one-line script doesn't affect the process that invoked it. (However, it would affect any scripts that were called in turn by that script.)

If a given environment variable is desired for just one command, the syntax  may be used, with the syntax of a variable assignment (or multiple variable assignments) preceding a command on the same line. (Note that, despite using the syntax of a variable assignment, this is very different from a normal Bash variable assignment, in that the variable is automatically exported into the environment, and in that it only exists for the one command. If you want avoid the confusion of similar syntax doing dissimilar things, you can use the common Unix utility  for the same effect. That utility also makes it possible to remove an environment variable for one command — or even to remove all environment variables for one command.) If   already exists, and it's desired to include its actual value in the environment for just one command, that can be written as.

An aside: sometimes it's useful to put variable definitions — or function definitions — in one Bash script (say, ) that can be called by another Bash script (say,  ). We can see that simply invoking that other Bash script, as  or as , will not work: the variable definitions in   would not be seen by  , not even if we "exported" those definitions. (This is a common point of confusion:  exports variables into the environment so that other processes can see them, but they're still only seen by child processes, not by parents.) However, we can use the Bash built-in command   ("dot") or , which runs an external file almost as though it were a shell function. If  looks like this:

then this script:

will print.

Scope
We have now seen some of the vagaries of variable scope in Bash. To summarize what we've seen so far:


 * Regular Bash variables are scoped to the shell that contains them, including any subshells in that shell.
 * They are not visible to any child processes (that is, to external programs).
 * If they are created inside a subshell, they are not visible to the parent shell.
 * If they are modified inside a subshell, those modifications are not visible to the parent shell.
 * This is also true of functions, which in many ways are similar to regular Bash variables.
 * Function-calls are not inherently run in subshells.
 * A variable modification within a function is generally visible to the code that calls the function.
 * Bash variables that are exported into the environment are scoped to the shell that contains them, including any subshells or child processes in that shell.
 * The  built-in command can be used to export a variable into the environment. (There are other ways as well, but this is the most common way.)
 * They differ from non-exported variables only in that they are visible to child processes. In particular, they are still not visible to parent shells or parent processes.
 * External Bash scripts, like other external programs, are run in child processes. The  or   built-in command can be used to run such a script internally, in which case it's not inherently run in a subshell.

To this we now add:


 * Bash variables that are localized to a function-call are scoped to the function that contains them, including any functions called by that function.
 * The  built-in command can be used to localize one or more variables to a function-call, using the syntax   or  . (There are other ways as well — for example, the   built-in command has the same effect — but this is probably the most common way.)
 * They differ from non-localized variables only in that they disappear when their function-call ends. In particular, they still are visible to subshells and child function-calls. Furthermore, like non-localized variables, they can be exported into the environment so as to be seen by child processes as well.

In effect, using  to localize a variable to a function-call is like putting the function-call in a subshell, except that it only affects the one variable; other variables can still be left non-"local".

It's important to note that, although local variables in Bash are very useful, they are not quite as local as local variables in most other programming languages, in that they're seen by child function-calls. For example, this script:

will actually print  rather than. This is because the original value of  is hidden until   returns. (In programming language theory, a variable like  is said to be "dynamically scoped" rather than "lexically scoped".)

One difference between  and a subshell is that whereas a subshell initially takes its variables from its parent shell, a statement like   immediately hides the previous value of  ; that is,   becomes locally unset. If it is desired to initialize the local  to the value of the existing , we must explicitly specify that, by using a statement like.

When a function exits, variables regain the values they had before their  declarations (or they simply become unset, if they had previously been unset). Interestingly, this means that a script such as this one:

will actually print : the   statement in the function takes effect before the variable is localized, so the value   is what is restored when the function returns.

And since  is simply an executable command, a function can decide at execution-time whether to localize a given variable, so this script:

will actually print bar&#xA;baz