Environment Variables and PATH in Bash: export, env and Startup Files
On this page
Every process on Linux carries a small set of named values called environment variables. They tell programs where your home directory is, which editor you prefer, what language to use and, most importantly, where to look for commands. When a command works in one terminal but not in another, or works for you but not in cron or under sudo, the environment is usually the reason.
Viewing variables
$ echo "$HOME"
/home/alice
$ printenv HOME
/home/alice
$ printenv | less # everything in the environment
$ env | grep -i lang # search it
printenv and env show only exported environment variables. Bash also has ordinary shell variables that are not passed to other programs; set with no arguments lists both, plus all your functions, which is why its output is so long.
Some variables you will meet often:
| Variable | Holds |
|---|---|
HOME | Your home directory |
USER | Your user name |
SHELL | Your login shell |
PATH | Directories searched for commands |
EDITOR / VISUAL | Editor used by git, crontab and others |
LANG | Language and character encoding |
PWD | Current directory |
PS1 | Your prompt (a shell variable, usually not exported) |
Shell variables vs environment variables
An assignment creates a shell variable that only the current shell can see. export marks it to be copied into the environment of every program the shell starts:
$ GREETING="hi"
$ bash -c 'echo "child sees: [$GREETING]"'
child sees: []
$ export GREETING
$ bash -c 'echo "child sees: [$GREETING]"'
child sees: [hi]
You can assign and export in one step with export NAME=value. Remove a variable with unset NAME. Note that there are no spaces around =: NAME = value tries to run a command called NAME.
The copy only goes one way. A child process gets its own copy of the environment and cannot change the parent's. That is why running a script that sets variables does nothing to your terminal, while source script.sh (or . script.sh) runs it in the current shell and does.
Set a variable for one command
Put the assignment in front of the command. It applies to that command only and leaves your shell untouched:
$ FOO=bar bash -c 'echo "$FOO"'
bar
$ echo "after: [$FOO]"
after: []
This is the clean way to change behaviour for a single run, for example LC_ALL=C sort file for byte-order sorting, or TZ=UTC date to see the time in UTC. env -i command goes further and runs a command with an empty environment, which is useful for testing whether a script depends on something in yours.
How PATH works
PATH is a colon-separated list of directories. When you type a command name without a slash, Bash checks each directory in order and runs the first match. To see the list one per line:
$ echo "$PATH" | tr ':' '\n'
/usr/local/bin
/usr/bin
/bin
Order matters. Something in /usr/local/bin wins over a program of the same name in /usr/bin, which is how locally installed versions override system ones. To see which one Bash will run, and every candidate:
$ type mytool
mytool is /home/alice/bin/mytool
$ type -a ls
ls is /usr/bin/ls
ls is /bin/ls
$ type cd
cd is a shell builtin
type also reports aliases, functions and builtins, which which cannot see. In scripts, command -v name is the portable way to check whether a command exists.
Add a directory to PATH
A personal ~/bin or ~/.local/bin directory is the usual home for your own scripts. Make a script executable and it still will not run by name until its directory is in PATH:
$ mkdir -p ~/bin
$ printf '#!/bin/bash\necho "hello from mytool"\n' > ~/bin/mytool
$ chmod +x ~/bin/mytool
$ mytool
bash: mytool: command not found
$ export PATH="$HOME/bin:$PATH"
$ mytool
hello from mytool
Always include the old value with $PATH. Writing PATH=~/bin on its own replaces the whole list, and suddenly ls and every other command stop working in that terminal. If that happens, just open a new terminal.
Putting your directory first lets your scripts override system commands; putting it last (PATH="$PATH:$HOME/bin") does the opposite. If Bash keeps running an old location after you move a program, run hash -r to clear its lookup cache.
. (the current directory) in PATH, especially for root. Anyone who can write a file called ls into a directory you visit could get you to run it.Make changes permanent
Variables set at the prompt disappear when the terminal closes. To keep them, add the lines to a startup file. Which file depends on the kind of shell:
~/.bashrcis read by interactive non-login shells, which is what most terminal windows open. Aliases, functions and your prompt belong here.~/.bash_profile(or~/.profileif that does not exist) is read by login shells, such as an SSH session or a console login. Most distributions make it source~/.bashrctoo./etc/environmentand files in/etc/profile.d/apply to all users and need root.
For PATH and exported variables, ~/.profile or ~/.bash_profile is the traditional place, but putting the line in ~/.bashrc works in practice on most desktops and servers. Guard against adding the same directory again every time the file is re-read:
case ":$PATH:" in
*":$HOME/bin:"*) ;;
*) export PATH="$HOME/bin:$PATH" ;;
esac
After editing, run source ~/.bashrc or open a new terminal. The .bashrc guide covers the rest of that file, and the prompt generator builds a PS1 line to add to it.
Load a .env file
Many projects keep settings in a .env file of simple KEY=value lines. To load one into your shell and export every variable, turn on automatic export while sourcing it:
$ cat .env.sample
APP_ENV=dev
APP_PORT=8080
$ set -a; source .env.sample; set +a
$ bash -c 'echo "$APP_ENV:$APP_PORT"'
dev:8080
set -a exports every variable assigned until set +a. Only do this with files you trust, since sourcing runs the file as shell code. Values with spaces must be quoted inside the file.
/proc/PID/environ, and they can end up in crash reports. Keep secrets in files with tight permissions where you can.Why cron and sudo see a different environment
Cron runs jobs with a tiny environment and a minimal PATH, and it does not read your .bashrc. A script that works at the prompt can fail in cron with "command not found". Use full paths or set PATH at the top of the crontab; the cron guide shows how.
sudo also resets most of the environment and usually replaces PATH with a fixed secure_path. Use sudo env VAR=value command to pass a specific variable, or sudo -E to keep yours where the sudoers policy allows it.
Quick reference
| Goal | Command |
|---|---|
| Show one variable | printenv HOME |
| Show all exported | env |
| Set and export | export NAME=value |
| Remove | unset NAME |
| For one command | NAME=value command |
| PATH one per line | echo "$PATH" | tr ':' '\n' |
| Prepend to PATH | export PATH="$HOME/bin:$PATH" |
| Which command runs | type -a name |
| Reload startup file | source ~/.bashrc |
| Load a .env file | set -a; source .env; set +a |