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_ENVpoints 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, functionsRemember
- 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.