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