Skip to content
elephantoo

Environment variables & shell configuration

Lesson 17 of 31 14 min read

Shell vs environment variables, PATH, export, .bashrc vs .profile, aliases and a better prompt.


Every process carries a set of named values called environment variables: your home directory, your username, your language, and the all-important PATH that tells the shell where to find commands. Understanding them explains a lot of "it works in my terminal but not in cron/systemd/my script" mysteries. This lesson also shows how to customise Bash with ~/.bashrc.

Looking at variables#

Terminal
echo $HOME
echo $USER
printenv SHELL
env | head
Output
/home/ada
ada
/bin/bash

env (or printenv) with no arguments lists every environment variable. Some you'll meet constantly:

VariableHolds
HOMEYour home directory (what ~ expands to)
USERYour username
PATHDirectories searched for commands
SHELLYour login shell
PWD / OLDPWDCurrent / previous directory (cd - uses OLDPWD)
LANG, LC_*Language, character encoding and sorting rules
EDITOR / VISUALYour preferred text editor for git, crontab, etc.
TERMTerminal type (colours, key codes)
PS1The look of your prompt

Shell variables vs environment variables#

Creating a variable: no spaces around =.

Terminal
greeting="hi"
echo $greeting
bash -c 'echo child sees: [$greeting]'
Output
hi
child sees: []

greeting is a shell variable: it exists only inside this shell. The child process (bash -c) doesn't receive it. export promotes it to an environment variable, which is copied into every child:

Terminal
export greeting
bash -c 'echo child sees: [$greeting]'
Output
child sees: [hi]

Export only flows downwards: a child can never change its parent's variables. That's why cd inside a script doesn't move your terminal, and why you source files whose variables you want to keep.

Set a variable for one command only by putting it in front:

Terminal
GREETING=hey bash -c 'echo $GREETING'
echo "after: [$GREETING]"
Output
hey
after: []

You'll see this pattern everywhere: LANG=C sort file, DEBUG=1 ./app, PORT=3000 npm start. unset greeting deletes a variable.

Quoting and expanding#

  • "$var" expands the variable but keeps it as one word, even if it contains spaces. Quote variables by habit.
  • '$var' is literal text: no expansion.
  • ${var} separates the name from following text: echo "${name}_backup".

Handy expansions (more in the scripting lessons):

Terminal
echo ${name:-guest}      # use "guest" if name is unset or empty
name=asha
echo ${name:-guest}
echo ${#name}            # length
file=report.tar.gz
echo ${file%%.*} ${file#*.} ${file%.gz}
Output
guest
asha
4
report tar.gz report.tar

PATH: how commands are found#

Terminal
echo "$PATH" | tr ':' '\n'
Output
/usr/local/bin
/usr/bin
/bin

(Yours will be longer.) When you type mytool, Bash checks each directory in order and runs the first mytool it finds. If none has it: command not found. The current directory is not in PATH (a deliberate security choice), which is why you run local scripts as ./script.sh.

Add your own directory, keeping the existing value:

Terminal
mkdir -p ~/bin
printf '#!/bin/bash\necho hello from my tool\n' > ~/bin/mytool
chmod +x ~/bin/mytool
export PATH="$HOME/bin:$PATH"
mytool
Output
hello from my tool

Putting it first means your version wins over system ones; append ($PATH:$HOME/bin) if you want the opposite. Debian/Ubuntu's default ~/.profile already adds ~/bin and ~/.local/bin if they exist when you log in. pip install --user and many installers drop tools in ~/.local/bin.

Never write export PATH=~/bin. You'll lose /usr/bin, and even ls stops working in that terminal. (Fix: close the terminal, or export PATH=/usr/local/bin:/usr/bin:/bin.)

Startup files: .bashrc vs .profile#

Bash reads different files depending on how it starts:

Shell typeWhenReads (Debian/Ubuntu)
Login shellSSH login, console login, bash -l/etc/profile, then the first of ~/.bash_profile, ~/.bash_login, ~/.profile
Interactive non-loginA new terminal window on a desktop, typing bash/etc/bash.bashrc, then ~/.bashrc
Non-interactiveScripts, cron, bash -cNeither (only $BASH_ENV if set)

On Debian and Ubuntu, ~/.profile sources ~/.bashrc, so in practice:

  • Put aliases, functions, prompt and shell options in ~/.bashrc.
  • Put environment variables that GUI apps and logins also need (like PATH) in ~/.profile. If you SSH in, ~/.profile (or ~/.bash_profile) runs.

On Fedora, the equivalent pair is ~/.bash_profile (which sources ~/.bashrc) and ~/.bashrc. If you create a ~/.bash_profile on Ubuntu, Bash stops reading ~/.profile, a classic surprise.

After editing, reload in the current shell:

Terminal
source ~/.bashrc          # or: . ~/.bashrc

System-wide variables can go in /etc/environment (plain KEY=value lines, no export) or a script in /etc/profile.d/.

Aliases and a nicer shell#

Terminal
alias ll='ls -lah'
alias gs='git status'
alias ..='cd ..'
alias rm='rm -i'          # ask before deleting
type ll
Output
ll is aliased to `ls -lah'

alias lists them all and unalias ll removes one. Run the original command, bypassing an alias, with a backslash: \rm file. For anything with arguments in the middle, write a function in ~/.bashrc instead:

Terminal
mkcd() { mkdir -p "$1" && cd "$1"; }

A few ~/.bashrc lines worth adding:

Terminal
export EDITOR=nano                 # or vim
HISTSIZE=10000                     # remember more history
HISTCONTROL=ignoreboth:erasedups   # skip duplicates and lines starting with a space
shopt -s histappend                # don't overwrite history when several terminals close
PS1='\u@\h:\w\$ '                  # prompt: user@host:dir$

In PS1, \u is the user, \h the host, \w the working directory, and \$ shows # for root.

History tricks#

CommandDoes
history / history 20Show (the last 20) commands
!!Repeat the last command (sudo !! is a favourite)
!42Run history entry 42
!sshRun the most recent command starting with "ssh"
!$The last argument of the previous command: mkdir new && cd !$
Ctrl+RSearch history interactively

Start a command with a space (with ignoreboth set) to keep it out of history, which is useful when a command contains a password you couldn't avoid typing.

Common mistakes#

  • Spaces around =: name = asha tries to run a command called name.
  • Forgetting export, then wondering why a program can't see the variable.
  • Overwriting PATH instead of extending it.
  • Expecting cron or systemd to load your ~/.bashrc. They don't: set variables in the crontab or unit file, and use absolute paths.
  • Storing secrets in ~/.bashrc that then leak into every program you run. Use files with 600 permissions or a secrets manager.

What's next#

That completes the command-line power tools. Next you'll start running the system like an admin, beginning with package management: installing, updating and removing software with apt, dnf, Snap and Flatpak.

Check your understanding

Quick quiz

0/3 answered
  1. 1.You run greeting=hi and then start a script that prints $greeting. It prints nothing. Why?

  2. 2.You put your own tools in ~/bin. Which line in ~/.bashrc lets you run them by name while keeping all the normal commands?

  3. 3.You just added an alias to ~/.bashrc. How do you use it in the terminal you already have open?

Finished reading?

Mark this lesson complete to track your progress.