Environment variables and startup files

Bash · Practical · 10 min

Most 'works in my terminal but not in cron or CI' problems come down to the environment.

Shell variables live only in the current shell. Exported variables form the environment, which is copied into every child process, so programs and scripts you start can read them. export NAME=value creates and exports in one step; env or printenv lists the environment. Changes in a child never flow back to the parent.

Prefix a command with assignments to set them for that command only: TZ=UTC date or LC_ALL=C sort file. This is how you pass configuration to tools without polluting your shell.

PATH is a colon-separated list of directories searched for commands, in order. Add your own with export PATH="$HOME/.local/bin:$PATH" (prepend to take priority). After installing something new, hash -r clears bash's remembered locations.

Startup files:

  • Login shells (SSH sessions, a console login, bash -l) read /etc/profile, then the first of ~/.bash_profile, ~/.bash_login, ~/.profile.
  • Interactive non-login shells (most terminal tabs on Linux) read ~/.bashrc.
  • Scripts read neither, unless BASH_ENV points at a file.

The usual setup: put aliases, prompt and functions in ~/.bashrc, and make ~/.bash_profile source it. Cron jobs and systemd services start with a minimal environment, so scripts should set the PATH and variables they rely on.

Example

greeting=hi                                 # shell variable only
bash -c 'echo "child sees: [$greeting]"'    # child sees: []
export greeting
bash -c 'echo "child sees: [$greeting]"'    # child sees: [hi]
MODE=fast bash -c 'echo "mode=$MODE"'       # mode=fast  (set for ONE command)
echo "mode=[${MODE:-}]"                     # mode=[]    (not set in this shell)
echo "$PATH" | tr ':' '\n' | head -n 3      # first directories searched
export PATH="$HOME/.local/bin:$PATH"        # prepend your own bin
printenv HOME                               # /home/you
# ~/.bash_profile (login shells) typically contains just:
#   [[ -f ~/.bashrc ]] && . ~/.bashrc
# ~/.bashrc (interactive shells): aliases, prompt, functions

Remember

  • export copies a variable into the environment of child processes
  • VAR=value cmd sets a variable for one command only
  • Login shells read ~/.bash_profile, interactive ones ~/.bashrc, scripts neither

Watch out for

  • Putting PATH changes only in ~/.bashrc and wondering why a cron job or IDE does not see them.

Check yourself

  1. A script runs `export DEBUG=1`. Is DEBUG set in the terminal that launched the script afterwards?
  2. Which file does a new interactive, non-login bash on Linux read?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: www.gnu.org/software/bash/manual/bash.html#Bash-Startup-Files