Linux & Bash · Part I · Module 10 · The Environment, PATH and Shell Startup
The Environment: Variables a Program Inherits
Every program you run starts with a list of name-and-value pairs handed to it by whatever started it. That list is the environment, and almost every question of the form 'why does this setting work here but not there' is a question about which process got a copy of what.
Intermediate18 min readLinuxPart IFundamentalsEnvironmentBash
Part I · Module 10 · Chapter 10.1 · The Environment: Variables a Program Inherits
Chapter 8.1 said a plain assignment is local to one shell and export puts a value where children can see it. Chapter 7.4 said a child gets a copy and nothing comes back. This module is where those two facts become the subject rather than an aside — because between them they explain why a setting that plainly works in your terminal has no effect in a script, and vice versa.
1. Two Kinds of Variable
$ LOCAL=a
$ export SHARED=b
$ bash -c 'echo "child: LOCAL=[$LOCAL] SHARED=[$SHARED]"'
child: LOCAL=[] SHARED=[b]Both variables exist in your shell. Only one of them reached the child.
- A shell variable is a name and value the shell remembers. It is yours alone.
- An environment variable is a shell variable that has additionally been marked for export, which places it in the list handed to every program you start.
The two commands that show you each list:
$ env | grep -E '^(LOCAL|SHARED)='
SHARED=b
$ set | grep -E '^(LOCAL|SHARED)='
LOCAL=a
SHARED=benv prints the environment — the exported ones only. set prints every shell variable, exported or not (along with functions, which is why it is usually piped somewhere).
2. Exporting
Three spellings, all equivalent:
export TOOL_HOME=/opt/tool # assign and export together
TOOL_HOME=/opt/tool; export TOOL_HOME # assign, then export
declare -x TOOL_HOME=/opt/tool # the same thing, spelled differentlyExport is a property of the variable, not of the value. Once a variable is exported it stays exported, and later assignments to it are automatically visible to children:
$ V=1
$ bash -c 'echo "pre-export child=[$V]"'
pre-export child=[]
$ export V
$ bash -c 'echo "post-export child=[$V]"'
post-export child=[1]Note that export V on its own — with no value — promoted a variable that already existed. That is a legitimate and occasionally useful form.
The reverse exists too:
$ export -n V
$ bash -c 'echo "after -n child=[$V]"'
after -n child=[]
$ echo "parent still V=[$V]"
parent still V=[1]export -n removes the export mark without unsetting the variable. The value is still yours; it simply stops being handed down. (unset V removes it entirely.)
To see what is currently exported, with quoting that can be pasted back:
$ export -p | grep ' T='
declare -x T="/opt/tool"3. Why Nothing Comes Back
This is the consequence that matters most, and it is worth proving rather than asserting:
$ export V=parent
$ bash -c 'export V=child'
$ echo "after the child exported: V=[$V]"
after the child exported: V=[parent]The child did everything right. It exported. It succeeded. And your shell is unchanged, because the child was working on its copy.
4. Changing It for One Command
Because the environment is copied at the moment a program starts, you can change what gets copied without changing your own:
$ export V=orig
$ env V=temp bash -c 'echo "inner=[$V]"'
inner=[temp]
$ echo "outer=[$V]"
outer=[orig]env NAME=value command runs that command with an altered environment and leaves yours alone. The shell has its own shorter spelling of the same idea, the assignment-prefix form from Chapter 8.1:
$ V=temp bash -c 'echo "inner=[$V]"'
inner=[temp]Both are the right tool for "run this one thing differently" — a different tool version, a debug flag, a language setting:
LC_ALL=C sort data.txt # byte order, not the locale's collation
TOOL_HOME=/opt/tool-2.1 ./run.sh # this run only
DEBUG=1 make # one noisy build5. The Variables Worth Knowing
$ env | sort | head -12Most of what you see is noise from your desktop session. About a dozen matter.
| Variable | Holds |
|---|---|
PATH | Where to look for programs — Chapter 10.2 |
HOME | Your home directory; what ~ expands to |
USER | Your login name |
SHELL | Your login shell, not necessarily the one running |
PWD | The current directory, maintained by the shell |
OLDPWD | The previous one — what cd - uses |
TERM | The terminal type, which full-screen programs need |
LANG / LC_* | Language and locale — affects sorting and messages |
EDITOR / VISUAL | Which editor other programs should open |
TMPDIR | Where temporary files should go |
HOSTNAME | The machine's name |
Where engineering settings live
In real work the environment is how tools are configured, and the names are conventional rather than standard:
export VCS_HOME=/opt/synopsys/vcs/2024.09
export LM_LICENSE_FILE=27000@licsrv1:27000@licsrv2
export PATH="$VCS_HOME/bin:$PATH"
export UVM_HOME="$VCS_HOME/etc/uvm"Note the third line building on the first, and the colon-separated list in the second — LM_LICENSE_FILE uses the same colon convention as PATH, and for the same reason: an ordered list of places to try. Module 25 covers EDA environments properly.
6. The Environment Is Only Strings
There are no types, no numbers, no lists, no nesting. Every value is text, exactly as Chapter 8.1 said of shell variables.
Two consequences:
A "list" is a string with a separator by convention. PATH uses colons; so does LM_LICENSE_FILE. Nothing enforces it — the programs that read them agree to split on colons, and that agreement is the whole mechanism.
A Bash array cannot be exported.
$ arr=(one two three)
$ export arr
$ bash -c 'echo "child sees: [$arr]"'
child sees: [one]The child received only the first element, because the environment has no way to represent an array — the child's shell saw a plain string. Arrays are Chapter 19.2; the environment-level fact is that they stop at the process boundary.
7. Diagnosing the Usual Problem
"It works in my terminal but not in the script" is almost always one of four things, and you can tell them apart in a minute.
Is it exported at all?
$ echo "$TOOL_HOME"
/opt/tool
$ env | grep TOOL_HOMEA value you can see but env cannot means it is a shell variable, not an environment one. Add export.
Is it exported in the right place? A setting typed into one terminal is in that terminal only. A new terminal, a scheduled job and a remote connection each get a fresh shell — which is Chapter 10.3.
Is something later overwriting it? Check what the program actually received rather than what you think you set:
env | sort > /tmp/before.txt
./setup.sh # or whatever you suspect
env | sort > /tmp/after.txt
diff /tmp/before.txt /tmp/after.txtIs the environment being scrubbed? sudo, cron, systemd and most continuous-integration runners deliberately start with a reduced environment. sudo in particular resets PATH by default — so a program you can run is one sudo cannot find. sudo -E preserves the environment where policy allows it.
Practice
1 — See both lists.
LOCAL=a; export SHARED=b
env | grep -E '^(LOCAL|SHARED)='
set | grep -E '^(LOCAL|SHARED)='
bash -c 'echo "child: LOCAL=[$LOCAL] SHARED=[$SHARED]"'2 — Promote and demote.
V=1
bash -c 'echo "before: [$V]"'
export V; bash -c 'echo "after export: [$V]"'
export -n V; bash -c 'echo "after -n: [$V]"'
echo "parent still has [$V]"3 — Prove nothing comes back.
export V=parent
bash -c 'export V=child; echo " child set it to $V"'
echo "parent: $V"4 — Change one command's environment.
export V=orig
V=temp bash -c 'echo "inner=[$V]"'
env V=temp bash -c 'echo "inner=[$V]"'
echo "outer=[$V]"5 — Strip it bare.
env -i bash -c 'echo "HOME=[$HOME]"; echo "PATH=[$PATH]"; ls /tmp >/dev/null && echo "ls still found"'6 — Catch SHELL lying.
echo "SHELL says: $SHELL"
echo "actually running: $(ps -p $$ -o comm=)"7 — Watch an array fail to cross.
arr=(one two three)
export arr
bash -c 'echo "child sees: [$arr]"'8 — Diff an environment.
env | sort > /tmp/before.txt
export NEWTHING=1
env | sort > /tmp/after.txt
diff /tmp/before.txt /tmp/after.txt9 — Look at your own, if you are on Linux.
cat /proc/$$/environ | tr '\0' '\n' | sort | headCheck Your Understanding
1. TOOL=/opt/tool then ./run.sh, and the script reports TOOL as empty. What is missing?
2. What is the difference between env and set?
3. A script does export RESULTS=/tmp/out as its last line. After running it, echo $RESULTS prints nothing. Why does export not help?
4. What does export -n V do that unset V does not?
5. You run env -i ./tool and it fails with "command not found" for something that works normally. What happened?
6. Why should a password never live in an environment variable on a shared machine?
8. Remember
- A variable can also be set, exported and wrong — Chapter 33.4 measures one pointing at a real project area that was not the intended one, reporting
TEST PASSEDfor a design that was never tested. - A shell variable lives in one shell; an exported one is placed in the environment.
- The environment is a list of strings copied into a program when it starts. Copied, not shared.
envshows the exported ones;setshows all of them.exportis a property of the variable, so later assignments are exported automatically.export Vpromotes an existing variable;export -n Vdemotes it without unsetting it.- No child can change its parent's environment. There is no mechanism, and that is a safety property as much as a limitation.
VAR=value commandandenv VAR=value commandchange one command's environment and leave yours alone.env -iempties it, though Bash still supplies a minimal defaultPATH— which is how build systems andcronfail.$SHELLis your login shell, not the one running. Useps -p $$ -o comm=.- Everything is a string. A "list" is a colon-separated string by convention, and a Bash array cannot cross the boundary.
- The environment is readable by other processes. Never put secrets in it.
- "Works in my terminal, not in the script" is: not exported, exported in the wrong place, overwritten later, or deliberately scrubbed. When a script reads a setting from the environment, Chapter 21.3 states the precedence that decides which source wins.
- A licence variable is a colon-separated search list, not a path — Chapter 25.4 takes it apart, including the four distinct ways it fails.
The most important environment variable of all decides which program runs when you type a name. The next chapter is PATH — how it is searched, how to prove which copy of a command you are getting, and how to add to it without destroying what was already there.
Where this fits
Part of the Linux & Bash curriculum.
