21

The Environment

Environment variables are how the shell and programs share configuration.

Every process on a Unix system carries a bag of named values. These values -- called environment variables -- tell programs where to find things, how to behave, and who is running them. They are the primary mechanism for passing configuration from a parent process to its children without modifying any files or passing command-line arguments.

You have already seen one: PATH, the list of directories the shell searches for programs. Now we will look at the full picture.

What an Environment Variable Is

Think of an environment variable as a sticky note attached to a process. It has a name and a value, both plain text. The name is conventionally uppercase. The value can be anything: a directory path, a number, a comma-separated list, an empty string.

$ echo $HOME
/home/user
$ echo $USER
user
$ echo $SHELL
/bin/bash

The $ prefix tells the shell to substitute the variable's value in place of its name. Without the $, the shell treats the text as a literal string:

$ echo HOME
HOME
$ echo $HOME
/home/user
Key term: Environment variable A named key-value pair carried by every process. Environment variables are inherited by child processes and are the standard Unix mechanism for passing configuration without using files or command-line flags. By convention, names are uppercase with underscores.

You can list every environment variable currently set in your shell:

$ env
HOME=/home/user
PATH=/usr/local/bin:/usr/bin:/bin
SHELL=/bin/bash
USER=user
TERM=xterm-256color
LANG=en_US.UTF-8
...

There may be dozens or even hundreds. Each one was set by something during your login process or shell startup.

Fig. 21.0 -- The environment as a key-value store

Process Environment Block

NAME VALUE HOME /home/user PATH /usr/local/bin:/usr/bin:/bin USER user SHELL /bin/bash LANG en_US.UTF-8 TERM xterm-256color

... and dozens more

Every process gets its own copy of this table at birth.

The environment is a table of name-value pairs. Each process gets its own copy when it is created. Changes to one process's environment do not affect any other process.

Shell Variables vs. Environment Variables

The shell actually maintains two kinds of variables. Understanding the difference is important.

A shell variable exists only inside the shell process. It is not passed to child processes. You create one by simple assignment:

$ color=blue
$ echo $color
blue

An environment variable is a shell variable that has been marked for export. It is copied into the environment of every child process the shell starts. You mark a variable for export with the export command:

$ export color=blue

Or export an existing variable:

$ color=blue
$ export color

The difference matters when you run programs. A program started by the shell only sees exported variables:

$ secret=hidden
$ export visible=shown
$ bash -c 'echo "secret=$secret visible=$visible"'
secret= visible=shown

The inner bash (a child process) can see visible because it was exported. It cannot see secret because it was a plain shell variable.

Key term: export A shell built-in command that marks a variable for inclusion in the environment of child processes. Without export, a variable exists only within the current shell. With export, it is inherited by every program the shell launches.
Fig. 21.1 -- Shell variables vs. environment variables
Parent shell (bash)

Shell variables: secret=hidden temp=working

Environment variables: PATH=/usr/bin:... visible=shown

fork+exec Child process (ls)

Inherited environment: PATH=/usr/bin:... visible=shown

NOT inherited: secret (not exported) temp (not exported)

Exported (inherited by children) Not exported (shell-only, invisible to children)

The child gets a COPY. Changes in the child do not affect the parent.

When the shell forks a child process, only exported variables are copied into the child's environment. Plain shell variables stay behind in the parent. The child gets a copy, not a reference -- changes in the child never propagate back to the parent.
Inheritance flows one way: from parent to child. A child process can never modify its parent's environment. If you set a variable inside a script, the calling shell will not see the change after the script exits. This is a common source of confusion for beginners.

The Essential Variables

Some environment variables are so common that nearly every program recognizes them. Here are the ones you need to know:

PATH

The most important environment variable. It contains a colon-separated list of directories that the shell searches when you type a command name. We covered this in Article 18.

$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

To add a directory, you append to PATH:

$ export PATH="$PATH:/home/user/bin"

The $PATH expands to the current value, and you concatenate your new directory after a colon. Put your addition at the end so that system programs are found first, or at the beginning if you want your version to take priority.

HOME

The path to the current user's home directory. Programs use it to find configuration files. The ~ shortcut in the shell expands to this value.

$ echo $HOME
/home/user
$ echo ~
/home/user

USER

The name of the currently logged-in user. Programs use it for logging, file ownership, and access control decisions.

SHELL

The path to the user's default login shell, as set in /etc/passwd. Note that this is the default shell, not necessarily the shell currently running. If you start zsh from inside bash, $SHELL still says /bin/bash.

EDITOR and VISUAL

The user's preferred text editor. Many programs (like git commit with no -m flag) consult these variables to decide which editor to open.

$ export EDITOR=vim

LANG and LC_*

Language and locale settings. They control how programs display dates, numbers, currency, and sort text. LANG=en_US.UTF-8 means English, United States, using the UTF-8 character encoding.

TERM

The type of terminal emulator. Programs that draw to the screen (like vim or top) read this to know which escape codes to use for colors, cursor movement, and screen clearing.

Setting Variables for a Single Command

You can set an environment variable for just one command without affecting your shell session:

$ LANG=C sort data.txt

The LANG=C assignment is placed before the command name. The variable is set only in the environment of sort and reverts to its previous value (or unset state) when sort exits.

This pattern is useful for overriding behavior temporarily:

$ TZ=UTC date
$ LC_ALL=C grep "[A-Z]" file.txt

How the Environment Is Built at Login

When you log in, the environment does not appear from nowhere. It is constructed in layers, with each layer adding or overriding variables.

Layer 1: The kernel. The kernel sets a minimal environment when the login process starts. This includes the terminal type and some basic system information.

Layer 2: PAM and the login process. The login system reads /etc/environment and sets variables found there. This is a system-wide configuration file.

Layer 3: The login shell. If you are using bash, the login shell reads /etc/profile (system-wide defaults), then one of ~/.bash_profile, ~/.bash_login, or ~/.profile (user-specific, in that priority order). These scripts typically set PATH, EDITOR, and other session-wide variables.

Layer 4: Interactive shell startup. Each new interactive bash shell (like opening a new terminal tab) reads ~/.bashrc. This file typically contains aliases, shell functions, prompt customization, and other per-shell settings.

Fig. 21.2 -- Startup file loading order for bash

Login shell (first shell after login)

/etc/environment /etc/profile ~/.bash_profile

(often sources ~/.bashrc)

Non-login interactive (new terminal window)

~/.bashrc

That's it.

# Typical ~/.bash_profile export PATH="$HOME/bin:$PATH" export EDITOR=vim source ~/.bashrc # load aliases and prompt
A login shell reads system-wide files first, then user-specific files. A non-login interactive shell reads only ~/.bashrc. A common pattern is for ~/.bash_profile to source ~/.bashrc so that all settings are available in both scenarios.

The usual best practice is to put environment variable exports (PATH, EDITOR, etc.) in ~/.bash_profile or ~/.profile, and put aliases, functions, and prompt settings in ~/.bashrc. Then have ~/.bash_profile source ~/.bashrc:

# In ~/.bash_profile:
export PATH="$HOME/bin:$PATH"
export EDITOR=vim

# Load interactive settings too
if [ -f ~/.bashrc ]; then
    source ~/.bashrc
fi
Key term: source A shell built-in that reads and executes a file in the current shell, rather than starting a new process. Unlike running a script normally, sourcing preserves variable changes and function definitions in the current shell. The dot command (.) is an alias for source.

Unsetting Variables

To remove a variable from the environment, use unset:

$ export DEBUG=1
$ echo $DEBUG
1
$ unset DEBUG
$ echo $DEBUG

After unset, the variable no longer exists. This is different from setting it to an empty string:

$ export DEBUG=""

An empty variable still exists in the environment. An unset variable is absent entirely. Some programs treat these differently.

A Process Cannot Modify Its Parent

This point is so important it deserves its own section. When a process is created with fork(), it gets a copy of the parent's environment. Changes to that copy are private. They do not propagate back to the parent.

This means if you run a script that sets a variable, the variable disappears when the script exits:

$ cat set_color.sh
#!/bin/bash
export COLOR=red
echo "Inside script: COLOR=$COLOR"

$ bash set_color.sh
Inside script: COLOR=red
$ echo $COLOR

$

The variable COLOR was set inside the child bash process that ran the script. When that process exited, its environment was destroyed. The parent shell never saw the change.

If you want a script to modify the current shell's environment, you must source it:

$ source set_color.sh
Inside script: COLOR=red
$ echo $COLOR
red

Sourcing reads the file and executes it in the current shell, not in a child. Now the export COLOR=red command runs in your actual shell, so the variable persists.

Fig. 21.3 -- Running vs. sourcing a script

bash script.sh

Parent shell COLOR=(unset) unchanged fork Child bash COLOR=red dies on exit

X no return

source script.sh

Same shell process script runs HERE COLOR=red persists bash script.sh New process. Variable changes are lost when script exits. source script.sh Same process. Variable changes persist in the current shell.
Running a script with "bash script.sh" creates a child process. Variables set inside it vanish when it exits. Sourcing with "source script.sh" (or ". script.sh") executes the commands in your current shell, so changes stick.
If you want a script to set variables in your current shell, source it. If you want a script's variable changes to be isolated and temporary, run it normally. This is not a limitation -- it is a safety feature. Scripts cannot silently alter your working environment unless you explicitly allow it.

Practical Patterns

Add a directory to PATH persistently. Edit ~/.bash_profile (or ~/.profile) and add:

export PATH="$HOME/.local/bin:$PATH"

Then either log out and back in, or source the file: source ~/.bash_profile.

Set a variable for development. Many programs read environment variables for configuration:

$ export DATABASE_URL="postgres://localhost/mydb"
$ export DEBUG=1
$ ./my-app

Check if a variable is set. Use the -z test (we will use this more in Article 22):

$ if [ -z "$DATABASE_URL" ]; then echo "Not set"; fi

Pass secrets without command-line arguments. Command-line arguments are visible in ps output. Environment variables are not (by default). This is why database passwords and API keys are typically passed as environment variables rather than flags.

What You Have Learned

Environment variables are key-value pairs carried by every process. The shell maintains both shell-only variables and exported environment variables. Only exported variables are inherited by child processes. Inheritance flows one way -- children cannot modify their parent's environment. The startup files ~/.bash_profile and ~/.bashrc build your environment at login and shell startup. The source command executes a file in the current shell, which is the only way a script can modify the current shell's variables.

The environment is the invisible layer of configuration that sits between the operating system and every program you run. Understanding it demystifies why things "just work" -- and, more importantly, why they sometimes do not.

Next: Your First Shell Script