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.
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
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:
- No spaces around
=.export NAME=valueworks.export NAME = valueis a syntax error — the shell interprets the spaces as argument boundaries. - The value is just a string. Numbers, paths, JSON — all strings.
exportis required if you want child processes to see the variable. A bareNAME=valueassignment 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.
“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”).
| Shell | File 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