Differences between revisions 1 and 12 (spanning 11 versions)
Revision 1 as of 2012-01-10 11:03:29
Size: 6825
Editor: Lhunath
Comment: Explain environment variables a little.
Revision 12 as of 2013-09-14 04:17:56
Size: 7946
Editor: Tyrmored
Comment: misguided efforts to fix possessive apostrophes
Deletions are marked like this. Additions are marked like this.
Line 5: Line 5:
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' variables. Bash lets you assign strings to its '''own''' env vars by linking a parameter with the same name to it using `export`.
Line 7: Line 11:
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' environment. As a result, the new process has an environment that is identical to, but a copy of, the original process. The environment is an area of memory for each and every [[ProcessManagement|process]]. When the process creates a new process (forks), the fork's environment is generally copied from the old process' environment. As a result, the new process has an environment that is identical to, but a copy of, the original process.
Line 15: Line 19:
The following Bash code may help you visualize things: 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 to the '''current shell's output''', and the tag "child" to mark output generated by a '''child process'''.
Line 18: Line 22:
# First, let's show what the Bash parameters MY_ENV_VAR and myParameter contain
# in the beginning.
# Assuming the parent of this script didn't have said variables in their environment,
# the script's Bash process will not have them in its environment, and these
# parameters will not yet exist. Hence, they will expand empty.
# In our output, we'll use the tag "parent" to refer to this script's process,
# and the tag "child" to refer to output generated by a child of this script.
unset MY_ENV_VAR myParameter # Remove our variables (also from environment)
Line 27: Line 25:
}}}
Line 28: Line 27:
# Now, let's create some variables. This command will create a Bash parameter called
#
MY_ENV_VAR and one called myParameter. It will then link MY_ENV_VAR to its environment,
#
causing an env var of the same name to be created with matching content. We will
# not
export myParameter, so that one exists purely as a Bash parameter.
Now, let's create some variables. These commands will create two '''Bash parameters''' called `MY_ENV_VAR` and `myParameter`, and then '''link''' `MY_ENV_VAR` to Bash's '''environment''', causing it to '''create an env var of the same name with matching content'''. We will ''not'' `export myParameter`, so ''that one exists purely as a Bash parameter''.  The '''`declare -p`''' builtin command shows this.

{{{
Line 37: Line 35:
declare -p MY_ENV_VAR myParameter # Prints: declare -x MY_ENV_VAR="Hello"
declare -p myParameter # Prints: declare -- myParameter="Hello"
}}}
Line 38: Line 39:
# Here's how we would update our environment variable. We basically update the
# Bash parameters. The parameter that's linked to the environment will cause Bash to
# update its env var of the same name as well.
Here's how we would '''update our env var'''. We basically '''update the Bash parameters'''. The parameter that's linked to the environment will cause Bash to update its env var of the same name as well (if a variable is already exported, you don't even really need `export` again).

{{{
Line 42: Line 43:
myParameter=Bye
Line 44: Line 46:
}}}
Line 45: Line 48:
# Note that these 'echo' statements are expanding our Bash parameters, not
#
environment variables. That's why we see both expansions resulting in 'Bye' even
#
though only MY_ENV_VAR is linked to an environment variable.
Note that these `echo` statements are '''expanding our Bash parameters, not environment variables'''. That's why we see both expansions resulting in "Bye" even though only `MY_ENV_VAR` is linked to an environment variable.
Line 49: Line 50:
# Let's demonstrate the effects of the environment now.
#
As I said, when we create a new process, we copy our script's environment into the new
#
process' environment. Our script's environment holds only MY_ENV_VAR, not myParameter
#
(since we only exported the former).
#
Here, we create a new Bash process that runs some bash code which will expand our
#
parameters again.
# The new Bash process has noticed its environment contains an environment variable
# named
MY_ENV_VAR, so it has created a Bash parameter with the same name. Since there
# is no
myParameter on the environment, it hasn't created this parameter. It therefore
#
expands empty.
Let's demonstrate the effects of the environment now. As I said, when we '''create a new process''', we '''copy the environment''' into the ''new'' process' environment. The environment holds only `MY_ENV_VAR`, not `myParameter` (since we only exported the former). Here, we create a new Bash process that runs some bash code which will expand our parameters again.
The new Bash process has noticed its environment contains an environment variable named `MY_ENV_VAR`, so '''it has created a Bash parameter with the same name'''. Since there is no `myParameter` on the environment, ''it hasn't created this parameter'', and it expands empty.

{{{
Line 61: Line 56:
}}}
Line 62: Line 58:
# Our child has only MY_ENV_VAR but back in our script's process, we still have both
#
parameters.
Our child has only `MY_ENV_VAR` but back in our shell, we still have ''both'' parameters.
Line 65: Line 60:
# Let's demonstrate that a child Bash process' version of MY_ENV_VAR is not the same
# as our script
'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 environment variable in our
# own script's process, we still find the old valu
e:
#
The child can update its own environment, but that does not affect the parent's copy.
Let's demonstrate that a child Bash process' 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'''.

{{{
Line 78: Line 70:
}}}
Line 79: Line 72:
# 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 main script's copy.
# This change in the main script 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.
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'''.

{{{
Line 90: Line 78:
    sleep 2 # Wait 2 seconds.     sleep 5 # Wait 5 seconds. (adjust according to typing speed)
Line 93: Line 81:
sleep 1 # Wait only 1 second.
Line 97: Line 84:
# Now, you'll actually see the child's second 'echo' statement happening AFTER the
# parent's second 'echo'. This is the chronological 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.
# Output:

[parent] MY_ENV_VAR: Bye
[child] MY_ENV_VAR: Bye
[parent] MY_ENV_VAR: Hello again
[child] MY_ENV_VAR: Bye
Line 106: Line 91:

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
}}}

Environment Variables

Environment variables are cause of much confusion. People generally think of "the environment" to be 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' 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' 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 to the current shell's output, and the tag "child" to mark output generated by a child process.

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: 

Now, let's create some variables. These commands will create two Bash parameters called MY_ENV_VAR and myParameter, and then link MY_ENV_VAR to Bash's environment, causing it to create an env var of the same name with matching content. We will not export myParameter, so that one exists purely as a Bash parameter. The declare -p builtin command shows this.

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"

Here's how we would update our env var. We basically update the Bash parameters. The parameter that's linked to the environment will cause Bash to update its env var of the same name as well (if a variable is already exported, you don't even really need export again).

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

Note that these echo statements are expanding our Bash parameters, not environment variables. That's why we see both expansions resulting in "Bye" even though only MY_ENV_VAR is linked to an environment variable.

Let's demonstrate the effects of the environment now. As I said, when we create a new process, we copy the environment into the new process' environment. The environment holds only MY_ENV_VAR, not myParameter (since we only exported the former). Here, we create a new Bash process that runs some bash code which will expand our parameters again. The new Bash process has noticed its environment contains an environment variable named MY_ENV_VAR, so it has created a Bash parameter with the same name. Since there is no myParameter on the environment, it hasn't created this parameter, and it expands empty.

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' 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

Environment (last edited 2016-08-27 22:14:52 by GreyCat)