They are scoped by process
The other answerers helped me to understand that shell variable scope is about processes and their descendants.
When you type a command like ls
on the command line, you're actually forking a process to run the ls
program. The new process has your shell as its parent.
Any process can have its own "local" variables, which are not passed to child processes. It can also set "environment" variables, which are. Using export
creates an environment variable. In either case, unrelated processes (peers of the original) will not see the variable; we are only controlling what child processes see.
Suppose you have a bash shell, which we'll call A. You type bash
, which creates a child process bash shell, which we'll call B. Anything you called export
on in A will still be set in B.
Now, in B, you say FOO=b
. One of two things will happen:
- If B did not receive (from A) an environment variable called
FOO
, it will create a local variable. Children of B will not get it (unless B calls export
).
- If B did receive (from A) an environment variable callled
FOO
, it will modify it for itself and its subsequently forked children. Children of B will see the value that B assigned. However, this will not affect A at all.
Here's a quick demo.
FOO=a # set "local" environment variable
echo $FOO # 'a'
bash # forks a child process for the new shell
echo $FOO # not set
exit # return to original shell
echo $FOO # still 'a'
export FOO # make FOO an environment variable
bash # fork a new "child" shell
echo $FOO # outputs 'a'
FOO=b # modifies environment (not local) variable
bash # fork "grandchild" shell
echo $FOO # outputs 'b'
exit # back to child shell
exit # back to original shell
echo $FOO # outputs 'a'
All of this explains my original problem: I set GEM_HOME
in my shell, but when I called bundle install
, that created a child process. Because I hadn't used export
, the child process didn't receive the shell's GEM_HOME
.
Un-exporting
You can "un-export" a variable - prevent it from being passed to children - by using export -n FOO
.
export FOO=a # Set environment variable
bash # fork a shell
echo $FOO # outputs 'a'
export -n FOO # remove environment var for children
bash # fork a shell
echo $FOO # Not set
exit # back up a level
echo $FOO # outputs 'a' - still a local variable
FOO=bar
, that sets the value for the current shell process. If I then run a program like (bundle install
), that creates a child process, which doesn't get access toFOO
. But if I had saidexport FOO=bar
, the child process (and its descendants) would have access to it. One of them could, in turn, callexport FOO=buzz
to change the value for its descendants, or justFOO=buzz
to change the value only for itself. Is that about right? – Nathan Long Dec 24 '11 at 12:17