datarekha

Environment variables & PATH

Learn what environment variables are, how PATH controls where the shell finds commands, and why 'command not found' happens — and how to fix it.

8 min read Intermediate Command Line Lesson 11 of 14

What you'll learn

  • What environment variables are and how to read and set them
  • How PATH controls command resolution, left-to-right through directories
  • How to make changes persist across sessions using shell startup files

Before you start

The last lesson left two invisible mechanisms unexplained: how the shell knows which file git is, and where a script reads its secret API key from. Both have the same answer — the environment, a table of named variables every process inherits, with PATH (the command-finder) as its star. This lesson opens that table, and “command not found” turns from a mystery into a one-line fix.

What is an environment variable?

Every process on your computer runs inside an environment — a small table of key/value string pairs that the operating system hands to the process when it starts. These are called environment variables.

Programs read this table to configure themselves without needing command-line flags every time. Your text editor reads $EDITOR. Git reads $HOME. Servers read $PORT and $DATABASE_URL. The shell itself reads dozens of them.

Print your own home directory:

echo $HOME
/Users/alice

The $ prefix tells the shell to expand the variable — substitute its value before running the command.

To see the entire environment, use either of these:

env
printenv

Both dump every variable the current shell process inherited, one per line, in NAME=value format. The list is usually long — scroll up to browse it.

To look up a single variable:

printenv HOME
/Users/alice

Setting an environment variable

Use export to create or update a variable and make it visible to child processes (programs you launch from the shell):

export GREETING="hello world"
echo $GREETING
hello world

Three things to memorize:

  1. No spaces around =. export NAME=value works. export NAME = value is a syntax error — the shell interprets the spaces as argument boundaries.
  2. The value is just a string. Numbers, paths, JSON — all strings.
  3. export is required if you want child processes to see the variable. A bare NAME=value assignment stays local to the current shell.

What is PATH?

$PATH is an environment variable with one job: it is a colon-separated list of directory paths that the shell searches, in order, when you type a command name.

echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

When you type python3, the shell does not search the whole disk. It walks through each directory in $PATH, left to right, and checks whether a file named python3 exists and is executable. The first match wins.

You type:python31st dir/usr/local/binpython3 ✓2nd dir (skipped)/usr/binpython3 ✗3rd dir (skipped)/binpython3 ✗… more dirs/sbinpython3 ✗First match wins/usr/local/bin/python3
The shell walks PATH left-to-right and runs the first matching executable it finds. If none match, you get “command not found.”

“Command not found” means the shell walked every directory in your $PATH and found nothing. The tool is installed somewhere — just not in any of those directories.

Inspecting command resolution

Two commands let you see exactly where a command is found:

which python3
/usr/local/bin/python3
type python3
python3 is /usr/local/bin/python3

type is slightly more informative — it also tells you when a name is a shell builtin, a function, or an alias rather than an executable file.

Adding a directory to PATH

Say you have scripts in ~/bin and you want to run them by name. Add that directory to your PATH:

export PATH="$HOME/bin:$PATH"

Break this down:

  • $HOME/bin — the new directory you are adding
  • : — the colon separator
  • $PATH — the existing value, preserved

The new directory goes on the left, so it takes precedence over anything with the same name in later directories.

Making changes persist: startup files

Environment variables you export in a terminal session disappear when that session closes. To make them permanent, add the export line to your shell’s startup file (also called an rc file — “rc” stands for “run commands”).

ShellFile to edit
zsh (macOS default)~/.zshrc
bash (Linux default, older macOS)~/.bashrc and/or ~/.bash_profile

Open ~/.zshrc in any editor and add your export at the bottom:

export PATH="$HOME/bin:$PATH"
export EDITOR="vim"

Save the file. The change takes effect in every new shell session. To pick it up in the current session without opening a new tab, source the file:

source ~/.zshrc

Sourcing means “read this file and run every line as if I had typed it myself.” The word source can also be written as a single dot: . ~/.zshrc.

.env files for project secrets

For project-specific settings — API keys, database URLs, feature flags — teams conventionally use a .env file at the project root:

DATABASE_URL=postgres://localhost/myapp
API_KEY=abc123
DEBUG=true

This file is never committed to version control (add .env to your .gitignore). To load it into your current shell:

source .env

Many frameworks (Python python-dotenv, Node dotenv) load .env automatically when your app starts, so you often do not need to source it manually.

Quick reference

echo $HOME               # print one variable
env                      # print all variables
printenv VARNAME         # print one variable (no $ prefix needed)

export NAME=value        # set a variable (no spaces around =)
export PATH="~/bin:$PATH"  # prepend a directory to PATH

which cmd                # show the full path of a command
type cmd                 # show how the shell resolves a name

source ~/.zshrc          # reload startup file in current session

In one breath

Every process inherits an environment — a table of NAME=value string pairs (see it with env/printenv, read one with $NAME). Create them with export NAME=value (no spaces around =; export is what makes child processes see it). The star variable is PATH, a colon-separated list of directories the shell walks left to right to resolve a command — first match wins, and “command not found” just means none of them held it (which/type show where one resolves). Add a directory with export PATH="$HOME/bin:$PATH" — and never drop the trailing :$PATH, or you erase every standard command at once. These vanish when the session ends, so put them in a startup file (~/.zshrc / ~/.bashrc) and source it to apply now; keep project secrets in a gitignored .env.

Practice

Before the quiz, reason through a real install. You just built a tool at ~/tools/bin/deploy, but typing deploy gives command not found. What single export line fixes it for this session, where do you put that line so it survives a new terminal, and what one command applies it right now without opening a new tab? Then name the subtle disaster of writing export PATH="$HOME/tools/bin" instead.

Quick check

0/3
Q1You run `export PATH="/opt/mytool/bin"`. What is the most likely result?
Q2You add `export EDITOR=nano` to `~/.zshrc` and save the file. You open a new terminal tab. What do you need to do to make the current tab also use the new setting?
Q3A teammate gives you a Python script that needs the environment variable `API_KEY` set to `secret`. They will run it on a Linux server with bash. Where should they put the export line so it is available every time they log in — without hard-coding the secret in the script itself?

A question to carry forward

That closes the Power Tools — and notice the quiet assumption running under every lesson so far: that the machine you are working on is the one in front of you. Your files, your processes, your environment, all local. But that is almost never where the real work lives. The dataset sits on a GPU box down the hall; the bug you are chasing is on a production server in another country; the data you need is behind a web API with no website to click. The terminal’s true superpower is reach — the ability to open a shell on a machine you will never physically touch, copy files across the network to it, and talk to a web service with no browser at all. Stepping off your own machine and onto everyone else’s is where the final chapter, Beyond the Basics, begins — starting with ssh and curl.

Sign in to track your progress

Completed lessons, your XP, level, and streak save to your account — it's free and takes a few seconds.

Related lessons

Explore further

Glossary terms
Skip to content