Environment Variables

Reviewed & published by Brayan K

By the end of this lesson you'll understand how the shell stores values, the difference between a shell variable and an environment variable, how PATH lets you run commands by name, and how to make your settings persist across every terminal session.

Part of the free Command Line course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn

1. Shell Variables vs Environment Variables

A plain assignment name=value creates a shell variable that only the current shell can see. To let a program you launch — a child process — inherit it, you must export it into the environment. That single distinction explains most "why can't my program see my variable?" confusion.

# A plain assignment makes a SHELL variable. No spaces around =.
greeting="hello"

# echo can read it in THIS shell...
echo "$greeting"

# ...but a child process (a new bash here) does NOT inherit it,
# because it was never exported. The child prints a blank line.
bash -c 'echo "child sees: [$greeting]"'

# 'export' promotes the variable INTO the environment, so any
# child process the shell launches will inherit a copy.
export greeting
bash -c 'echo "child sees: [$greeting]"'

# You can assign and export in one step:
export project="learn-cli"
bash -c 'echo "project is $project"'

2. Reading and Inspecting Variables

Expand a value with $VAR or ${VAR} — use the braces when the name touches other text. To list the whole environment use env (or printenv with no argument); to read one variable use printenv NAME. Note that printenv only sees exported variables, while echo $VAR expands any shell variable.

# Three ways to look at the environment.

# env  (and printenv with no args) lists EVERY environment variable.
# There are usually dozens, so here we just count them:
env | wc -l

echo "--- read one specific variable ---"
# printenv NAME prints just that one variable's value.
printenv HOME

echo "--- $VAR vs ${VAR} ---"
# $VAR and ${VAR} do the same thing: expand the value.
# Use the braces when the name touches other text, so the shell
# knows where the name ends.
user="ada"
echo "Home is $HOME"
echo "Backup dir: ${user}_backup"   # braces needed: user_backup would be one name

3. PATH: How the Shell Finds Commands

PATH is a colon-separated list of directories. When you type ls, the shell walks PATH left to right and runs the first ls it finds. To add your own folder, prepend it and keep the old value on the end with :$PATH — overwriting PATH entirely is how people accidentally make every command "not found".

# PATH is a COLON-separated list of directories. When you type a
# command, the shell searches these folders, left to right, for it.
echo "$PATH"

echo "--- which directory did a command come from? ---"
# 'which' (or 'type') shows the first match found on the PATH.
which ls

echo "--- add a directory to the FRONT of PATH ---"
# Put your own bin first so your scripts win. Keep the old PATH on
# the end with :$PATH so you don't lose the system directories.
export PATH="$HOME/bin:$PATH"
echo "$PATH"

4. The Exit Status: $?

The special variable $? holds the exit status of the command that just finished. By convention 0 means success and any non-zero value means a failure. Scripts lean on this constantly — it's how if decides whether the previous command worked.

# Every command sets $? to its exit status the moment it finishes.
# 0 means success; any non-zero value means some kind of failure.

ls /etc/hostname > /dev/null   # this file exists -> success
echo "after success: $?"       # 0

ls /no/such/file 2> /dev/null  # this fails -> non-zero
echo "after failure: $?"       # 2 (a typical 'No such file' code)

# A common pattern: act on whether the previous command worked.
if grep -q "root" /etc/passwd; then
  echo "found root, exit was $?"
fi

5. Removing Variables with unset

When you no longer need a variable — especially a secret like an API token — remove it with unset. After unsetting, expanding the name gives an empty string because the variable simply no longer exists.

# unset removes a variable completely.
export token="abc123"
echo "before: [$token]"

unset token
echo "after:  [$token]"        # empty — the variable is gone

# Unsetting is handy for secrets you no longer want in the
# environment, and for clearing a variable a child might inherit.

6. Persisting Variables Across Sessions

Anything you set in a shell vanishes when that shell closes. To load a variable every session, add the export line to a startup file. The right file depends on your shell and session type: ~/.bashrc for interactive non-login bash, ~/.bash_profile for login bash, and ~/.zshrc for zsh (the default on modern macOS). Use source to apply changes without opening a new terminal.

# Variables set in a shell vanish when that shell closes. To make
# one load EVERY session, add the export line to a startup file.
#
#   ~/.bashrc        bash: interactive NON-login shells (new terminal tabs on Linux)
#   ~/.bash_profile  bash: LOGIN shells (first login, many macOS terminals)
#   ~/.zshrc         zsh:  interactive shells (default shell on modern macOS)

# Append a line to ~/.bashrc (>> adds, > would overwrite — be careful):
echo 'export EDITOR=nano' >> ~/.bashrc

# Apply it to the CURRENT shell without opening a new terminal:
source ~/.bashrc          # 'source' (or '.') runs the file in this shell

echo "EDITOR is now $EDITOR"

🎯 Your Turn: export to a child process

Fill in the one blank marked ___ using the # 👉 hint so the child bash can see the variable, then run it and check the Output panel matches.

# 🎯 YOUR TURN — make a child process see your variable.

api_url="https://example.com"

# 1) Promote api_url into the environment so children inherit it.
___ api_url                    # 👉 replace ___ with  export

# 2) The child bash should now print the value.
bash -c 'echo "url: $api_url"'

# ✅ Expected output:
#    url: https://example.com

🎯 Your Turn: extend PATH safely

Add a new folder to the front of PATH while keeping everything that was already there. Fill in the blank, then run it.

# 🎯 YOUR TURN — add a folder to PATH without losing the old one.

# 1) Prepend ~/tools but KEEP the existing PATH on the end.
export PATH="$HOME/tools:___"   # 👉 replace ___ with  $PATH

echo "$PATH"

# ✅ Expected output (your existing PATH will follow the new folder):
#    /home/ada/tools:/usr/local/bin:/usr/bin:/bin

Pro Tips

Common Errors (and the fix)

📋 Quick Reference

TaskCommandNotes
Set shell variablename="value"No spaces around =
Export to environmentexport nameChildren inherit it
Read value$name / ${name}Braces near other text
List allenvprintenv also works
Read oneprintenv NAMEExported vars only
Command search path$PATHColon-separated dirs
Extend PATHexport PATH="dir:$PATH"Keep old value
Last exit status$?0 = success
Remove variableunset nameDeletes it
Persist a variableecho 'export X=1' >> ~/.bashrcLoads each session

Mini-Challenge: an environment report

No blanks this time — just a brief and an outline. Combine export, $?, PATH inspection, and unset into a few commands that demonstrate everything from this lesson, and check your output against the hints in the comments.

# 🎯 MINI-CHALLENGE: a tiny environment report
# Write a few commands (or a small script) that:
#
# 1. Sets a SHELL variable  app="demo"  and proves a child bash
#    does NOT see it (export it, then prove the child DOES).
# 2. Prints your HOME and the number of directories on your PATH
#    (hint: turn the colons into newlines, then wc -l).
# 3. Runs a command you know will fail and prints its $? exit code.
# 4. unset the app variable at the end.
#
# Hint for #2:  echo "$PATH" | tr ':' '\n' | wc -l

# your commands here

🎉 Lesson Complete!

Practice quiz

How do you read the value of an environment variable named HOME?

  • $HOME
  • &HOME
  • @HOME
  • *HOME

Answer: $HOME. Prefix the name with a dollar sign: $HOME (or ${HOME}).

Which command marks a shell variable so child processes can see it?

  • set
  • local
  • export
  • env

Answer: export. export promotes a shell variable into the environment so child processes inherit it.

What does the PATH variable hold?

  • The current user name
  • A colon-separated list of directories searched for commands
  • The shell prompt string
  • Your home directory

Answer: A colon-separated list of directories searched for commands. PATH is a colon-separated list of directories the shell searches to find executables.

Which command prints ALL current environment variables?

  • echo
  • printf
  • list
  • env

Answer: env. env (or printenv with no argument) prints every variable in the environment.

What does $? contain after a command finishes?

  • The exit status of the last command
  • The process ID
  • The current directory
  • The shell name

Answer: The exit status of the last command. $? holds the exit status of the most recent command: 0 means success, non-zero means failure.

What is the difference between a shell variable and an environment variable?

  • Shell variables are numbers, environment variables are text
  • A shell variable is local to the current shell until you export it into the environment
  • Environment variables cannot be changed
  • There is none

Answer: A shell variable is local to the current shell until you export it into the environment. A plain assignment makes a shell variable; export turns it into an environment variable inherited by child processes.

Which command removes a variable entirely?

  • delete VAR
  • rm VAR
  • clear VAR
  • unset VAR

Answer: unset VAR. unset VAR removes the variable from the shell and environment.

Why use printenv HOME instead of echo $HOME in scripts?

  • echo cannot read variables
  • There is no difference at all
  • printenv only prints variables that are actually exported in the environment
  • printenv is faster

Answer: printenv only prints variables that are actually exported in the environment. printenv reads the environment directly, so it shows nothing for an unexported shell variable, while echo $HOME expands any shell variable.

Which file is read for each new interactive non-login bash shell, such as a new terminal tab on Linux?

  • ~/.bash_profile
  • ~/.bashrc
  • /etc/shadow
  • ~/.ssh/config

Answer: ~/.bashrc. ~/.bashrc runs for interactive non-login shells; .bash_profile runs for login shells.

How do you make a variable persist across all future terminal sessions?

  • Add the export line to a startup file like ~/.bashrc or ~/.zshrc
  • Run set -x
  • Save it with the save command
  • Type export once in the current shell

Answer: Add the export line to a startup file like ~/.bashrc or ~/.zshrc. Variables set in the current shell vanish when it closes; add the export line to a startup file to load it every session.

Continue this course

Frequently asked questions

What is the difference between a shell variable and an environment variable?

A plain assignment like name="Ada" creates a SHELL variable that only the current shell can see. Running export name turns it into an ENVIRONMENT variable, which means every child process the shell launches inherits a copy. Programs you run can only read environment variables, not un-exported shell variables.

Why do I write $HOME but export HOME (no dollar sign)?

The dollar sign means 'give me the value of'. You use $HOME when you want the value, e.g. echo $HOME. But export, unset, and assignment all act on the NAME of the variable, so you write them without the dollar sign: export HOME, unset HOME, HOME=/tmp.

What exactly is PATH and why do commands break when I edit it wrong?

PATH is a colon-separated list of directories the shell searches to find the program for a command you type. If you overwrite it with export PATH="/my/dir" you wipe out /usr/bin and friends, so suddenly ls and grep are 'not found'. Always append the old value: export PATH="/my/dir:$PATH".

Which startup file should I put my exports in: .bashrc, .bash_profile, or .zshrc?

It depends on your shell and session type. ~/.bashrc runs for interactive non-login bash shells (new terminal tabs on Linux). ~/.bash_profile runs for login shells (first login, and many macOS terminal windows). If your shell is zsh (the default on modern macOS), use ~/.zshrc. When in doubt, run echo $0 to see your shell.

Why did my exported variable disappear after I closed the terminal?

export only affects the current shell and its children, and it all goes away when that shell exits. To make a variable load every time, add the export line to a startup file like ~/.bashrc or ~/.zshrc. Then either open a new terminal or run source ~/.bashrc to load it into the current one.