Environment Variables
Environment variables are the cause of much confusion. People generally think of "the environment" as a global system-wide pool of settings that processes dip into. This is incorrect.
To summarize:
Environment Variables (shown with env) are not the same thing as Bash Parameters (shown with set). They are not global (every process has their own set), and they are only copied from the parent when the child process is created. You cannot update another process's variables. Bash lets you assign strings to its own env vars by linking a parameter with the same name to it using export.
What is the "environment"
The environment is an area of memory for each and every process. When the process creates a new process (forks), the fork's environment is generally copied from the old process's environment. As a result, the new process has an environment that is identical to, but a copy of, the original process.
What this means to us Bash users, is that whenever we start a new process from our bash scripts, these processes will inherit our script's environment. It's important to know that we're talking about a copy here, and that this copying only ever happens during the creation of the new process.
The same process applies not only to processes started from your Bash scripts, but any processes started on your system: Each has inherited the environment of their parent process; added to, modified or removed stuff from their copy of it, and whatever children they spawned off themselves have inherited that modified environment.
That makes no sense. Illustrate!
First, let's make sure that the Bash parameters MY_ENV_VAR and myParameter do not already exist. Assuming the parent of the current shell didn't have said variables in their environment and you did not create them yet, this step is not really necessary (doesn't hurt though). In our output, we'll use the tag "parent" to mark the current shell's output, and the tag "child" to mark output generated by a child process's. Now, let's create some variables. These commands will create two Here's how we would Note that these echo statements are Let's demonstrate the effects of the environment now. As I said, when we unset MY_ENV_VAR myParameter # Remove our variables (also from environment)
echo "[parent] MY_ENV_VAR: $MY_ENV_VAR" # Prints: [parent] MY_ENV_VAR:
echo "[parent] myParameter: $myParameter" # Prints: [parent] myParameter:
MY_ENV_VAR=Hello # Create a parameter MY_ENV_VAR
myParameter=Hello # Create a parameter myParameter
export MY_ENV_VAR # Link MY_ENV_VAR to the environment
echo "[parent] MY_ENV_VAR: $MY_ENV_VAR" # Prints: [parent] MY_ENV_VAR: Hello
echo "[parent] myParameter: $myParameter" # Prints: [parent] myParameter: Hello
declare -p MY_ENV_VAR myParameter # Prints: declare -x MY_ENV_VAR="Hello"
declare -p myParameter # Prints: declare -- myParameter="Hello"
export MY_ENV_VAR=Bye
myParameter=Bye
echo "[parent] MY_ENV_VAR: $MY_ENV_VAR" # Prints: [parent] MY_ENV_VAR: Bye
echo "[parent] myParameter: $myParameter" # Prints: [parent] myParameter: Bye
bash -c 'echo "[child] MY_ENV_VAR: $MY_ENV_VAR"' # Prints: [child] MY_ENV_VAR: Bye bash -c 'echo "[child] myParameter: $myParameter"' # Prints: [child] myParameter:
Our child has only MY_ENV_VAR but back in our shell, we still have both parameters.
Let's demonstrate that a child Bash process's version of MY_ENV_VAR is not the same as the shell's version of it. Here, we modify MY_ENV_VAR in the child, which will update the child's environment variable of the same name. But when we look at the shell's environment variable, we still find the old value: The child can update its own environment, but that does not affect the parent's copy.
echo "[parent] MY_ENV_VAR: $MY_ENV_VAR" # Prints: [parent] MY_ENV_VAR: Bye bash -c ' echo "[child] MY_ENV_VAR: $MY_ENV_VAR" # Prints: [child] MY_ENV_VAR: Bye MY_ENV_VAR="Hello again" # Update the child's MY_ENV_VAR echo "[child] MY_ENV_VAR: $MY_ENV_VAR" # Prints: [child] MY_ENV_VAR: Hello again ' echo "[parent] MY_ENV_VAR: $MY_ENV_VAR" # Prints: [parent] MY_ENV_VAR: Bye
Let's try a trick now to demonstrate the reverse: Updating MY_ENV_VAR in the parent also doesn't update the child's MY_ENV_VAR. Here, we'll start a child process that shows its own version of MY_ENV_VAR, waits a little while, and then shows it again. While the child is waiting, we'll update MY_ENV_VAR in our shell's copy. This change will not affect our child's second output of MY_ENV_VAR, since the child's version of it is a copy at the time of the child's creation and any changes to it after it was copied are not carried over.
echo "[parent] MY_ENV_VAR: $MY_ENV_VAR" # Prints: [parent] MY_ENV_VAR: Bye bash -c ' echo "[child] MY_ENV_VAR: $MY_ENV_VAR" # Prints: [child] MY_ENV_VAR: Bye sleep 5 # Wait 5 seconds. (adjust according to typing speed) echo "[child] MY_ENV_VAR: $MY_ENV_VAR" # Prints: [child] MY_ENV_VAR: Bye ' & MY_ENV_VAR="Hello again" # Update the parent's MY_ENV_VAR echo "[parent] MY_ENV_VAR: $MY_ENV_VAR" # Prints: [parent] MY_ENV_VAR: Hello again # Output: [parent] MY_ENV_VAR: Bye [child] MY_ENV_VAR: Bye [parent] MY_ENV_VAR: Hello again [child] MY_ENV_VAR: Bye
As you can see, having updated the parent's MY_ENV_VAR while the child was waiting has not changed the child's version of MY_ENV_VAR.
But subshells are different
A SubShell is a child process, and so it inherits the parent's environment; but it's special, and it also inherits the parent's regular parameters.
$ unset myParameter $ myParameter=42 $ (echo "$myParameter") 42 $ bash -c 'echo "$myParameter"' $
However, the subshell still can't alter the parent's parameters, because it is still just a child.
$ myParameter=42 $ (myParameter=69) $ echo "$myParameter" 42