I just ran into a problem that shows me I'm not clear on the scope of shell variables.

I was trying to use bundle install, which is a Ruby command that uses the value of $GEM_HOME to do its work. I had set $GEM_HOME, but the command ignored that value until I used export, as in export GEM_HOME=/some/path.

I read that this makes the variable somehow "global" (also known as an environment variable), but I don't understand what that means. I know about globals in programming, but not across distinct programs.

Also, given that my setting such variables applies only to the current shell session, how would I set them for, say, a daemonized process?

4 Answers
4

The processes are organized as a tree: every process has a unique parent, apart from init which PID is always 1 and has no parent.

The creation of a new process goes generally through a pair of fork/execv system calls, where the environment of the child process is a copy of the parent process.

To put a variable in the environment from the shell you have to export that variable, so that it is visible recursively to all children. But be aware that if a child change the value of a variable, the changed value is only visible to it and all processes created after that change (being a copy, as previously said).

Take also into account that a child process could change its environment, for example could reset it to default values, as is probably done from login for example.

Ah! OK, let's see if I understand this. In the shell, if I say 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 to FOO. But if I had said export FOO=bar, the child process (and its descendants) would have access to it. One of them could, in turn, call export FOO=buzz to change the value for its descendants, or just FOO=buzz to change the value only for itself. Is that about right?
–
Nathan LongDec 24 '11 at 12:17

@NathanLong That's not exactly it: in all modern shells, a variable is either exported (and so any change in value is reflected in the environment of descendents) or not exported (meaning that the variable is not in the environment). In particular, if the variable is already in the environment when the shell starts, it is exported.
–
GillesDec 24 '11 at 16:02

As you can see, the global variable is displayed from all three locations, the unexported variables is not displayed outside the current shell and the function local variable has no value outside the function itself.

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.

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.

The variable set within a subshell or child shell is only visible to the subshell in which it is defined. The exported Variable is actually made to be an environment variable. So to be clear your bundle install executes its own shell which doesn't see the $GEM_HOME unless it is made an environment variable aka exported.