19  19. Environment Variables

Programs often need information that shouldn’t be typed into their code: where your home folder is, which folders contain commands, or a secret password for an online service. The shell keeps this kind of information in variables. This page explains shell variables and environment variables, why they are used, and how to make them permanent.

19.1 Shell variables

A variable is a name that holds a value. It’s like naming a cell in Excel (say, calling cell B2 “TaxRate”) so you can use the name instead of the value.

Create a variable with name=value, and read it by putting a $ in front of its name:

course="AI Assisted Development"
echo $course
echo "I am taking $course"

Output:

AI Assisted Development
I am taking AI Assisted Development

The value needs quotes because it contains spaces. Inside the double quotes in the last command, the shell replaced $course with the variable’s value.

When the variable name touches other text, put the name in curly braces, ${name}, to show where the name ends:

file=report
echo "$file_2026.txt"
echo "${file}_2026.txt"

Output:

.txt
report_2026.txt

In the first echo, the shell looked for a variable named file_2026 (underscores and digits can be part of a name). There is no such variable, so it was replaced with nothing.

Common mistakes with variables
  • No spaces around =. name = Maria doesn’t create a variable: the shell thinks name is a command and reports an error (command not found: name on a Mac, name: command not found in Git Bash).
  • Quote values that contain spaces. name=Maria Lopez fails with command not found for Lopez. Use name="Maria Lopez".
  • Names are case-sensitive. $Name and $name are different variables.
  • Typos fail silently. A variable that doesn’t exist is simply empty, so echo $corse (instead of $course) prints an empty line instead of an error.

19.2 Environment variables and export

Every program on your computer runs with a set of named values called its environment. Environment variables are variables that the shell passes on to every program it starts.

A variable you create with name=value is a shell variable: only the shell itself knows about it. The export command turns it into an environment variable, so that the programs you start can see it too.

The printenv command is a separate program that prints the value of an environment variable. Watch what it sees before and after export:

city="Boston"
printenv city
export city
printenv city

Output:

Boston

The first printenv city printed nothing, because city was only a shell variable. After export, the second one printed Boston.

Usually you create and export a variable in one step, and by convention environment variable names are written in UPPERCASE:

export CITY="Boston"

To remove a variable, use unset CITY.

  • Every running program is a process. When your shell starts a program, that program becomes a child process of the shell, and it receives a copy of the shell’s environment variables (but none of its plain shell variables).
  • The copy only goes one way. A child process can change its own copy, but it can never change the variables of the shell that started it. (That’s why a script that sets a variable doesn’t change your terminal, unless you run the script with source, which runs its lines in your current shell.)
  • echo $city works for both kinds of variables, because the shell replaces $city before echo even runs. To see whether a variable was exported, use printenv city or look for it in the output of env.
  • You can also set an environment variable for one single command by writing it in front of the command: GREETING=hello printenv GREETING prints hello, but afterwards printenv GREETING prints nothing.

19.3 Why environment variables are used

  1. To configure a program without changing its code. The same program can behave differently depending on its environment. A web app might read a variable such as DATABASE_URL and connect to a practice database on your laptop (“development”) but to the real customer database on the server (“production”).
  2. To keep secrets out of code. Passwords and API keys (secret codes that let your program use an online service, such as a payment or AI service) should never be typed into code, which may end up on GitHub for everyone to see. Instead the code reads them from the environment, for example os.environ["WEATHER_API_KEY"] in Python or process.env.WEATHER_API_KEY in JavaScript.
  3. To describe the computer and the user. Programs find your home folder through HOME, and the shell finds commands through PATH.

19.4 Common environment variables

Variable What it holds
HOME your home directory
USER your user name (in Git Bash: USERNAME)
PWD the current working directory (it changes every time you cd)
SHELL your default shell, the one a new terminal window starts
PATH the folders the shell searches for commands (see below)
LANG your language and character set, for example en_US.UTF-8

Try a few of them:

echo $HOME
echo $USER
echo $SHELL

Output:

/Users/maria
maria
/bin/zsh
echo $HOME
echo $USERNAME
echo $SHELL

Output:

/c/Users/sam
sam
/usr/bin/bash

In Git Bash, echo $USER usually prints an empty line, because Windows stores your user name in a variable called USERNAME. (The whoami command works on both systems.)

To list all your environment variables, type env (or printenv with no arguments). The list is long; most of the entries are used by programs behind the scenes.

19.5 PATH: how the shell finds commands

When you type a command such as ls, the shell doesn’t search your whole computer for it. It looks only in the folders listed in the PATH variable, in order, and runs the first program named ls that it finds.

echo $PATH

Output (something like this on a Mac; yours will probably be longer):

/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

The folders are separated by colons (:). In Git Bash the list is longer and includes Windows folders, written in the /c/... style.

The which command tells you where a command was found:

which ls

Output (Mac; in Git Bash it’s /usr/bin/ls):

/bin/ls

When you see command not found, there are three usual reasons: you made a typo, the program isn’t installed, or it is installed in a folder that isn’t on your PATH. That last one often happens right after installing a new tool, which is why installation instructions sometimes say “add this folder to your PATH”. (You will see this again in Installing Claude Code.)

To add a folder, put it in front of the existing list:

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

Read it as: “the new PATH is my bin folder, a colon, and then the old PATH.”

Don’t forget the old $PATH

export PATH="$HOME/bin" (without :$PATH at the end) replaces the whole list. After that, even ls gives “command not found”. If this happens, close the terminal window and open a new one; the change was only temporary.

Notice that your current folder is not on the PATH. That’s why you have to type ./ in front of a script in the current folder to run it (see Bash Scripts).

19.6 Making variables permanent

Variables that you set at the prompt disappear when you close the terminal window. Every new terminal window starts by running a startup file in your home folder, so to keep a setting, put its export line in that file.

For example, many programs check the EDITOR variable to decide which text editor to open. To set it permanently, open your startup file in nano (see below), add the line export EDITOR="nano" at the bottom, press Ctrl+O and Enter to save, and Ctrl+X to exit. Then open a new terminal window, or load the file into the current one with source:

zsh reads ~/.zshrc every time you open a new Terminal window. Open it with:

nano ~/.zshrc

After saving and exiting, load it with:

source ~/.zshrc

Put your settings in ~/.bashrc. Open it with:

nano ~/.bashrc

After saving and exiting, load it with:

source ~/.bashrc

When Git Bash starts, it actually reads ~/.bash_profile. If you have a ~/.bashrc but no ~/.bash_profile, the next new Git Bash window prints a WARNING about this and creates a ~/.bash_profile that loads your ~/.bashrc. That is expected, and it happens only once.

source runs the lines of a file in your current shell, so the new setting takes effect right away.

You can also add a line without opening an editor, using >> from Writing to Files (use ~/.bashrc instead of ~/.zshrc in Git Bash):

echo 'export EDITOR="nano"' >> ~/.zshrc

Use >>, never >, which would erase everything else in the file. The single quotes make sure the line is written exactly as typed. That matters as soon as a line contains $, as in export PATH="$HOME/bin:$PATH"; the next page, Single Quotes vs. Double Quotes, explains why.

19.7 API keys, .env files, and secrets

Projects usually keep their settings and keys in a text file named .env in the project folder, one NAME=value per line:

DATABASE_URL=postgres://localhost/club_app
WEATHER_API_KEY=paste-your-key-here

Many frameworks and tools load this file into the environment automatically when the app starts. Because a .env file holds secrets, it must never be committed to Git: see .gitignore and Keeping Secrets Out of Git and APIs, API Keys, and Third-Party Services.

Keep secrets secret
  • Never paste a real API key into code, a chat, an AI prompt, a screenshot, or a shared document.
  • Don’t print a key with echo while your screen is shared or recorded.
  • Commands you type are saved in your shell’s history file, so a key typed at the prompt ends up stored on disk in plain text.
  • If a key leaks, revoke it on the service’s website and create a new one.

19.7.1 Example: Claude Code and ANTHROPIC_API_KEY

As of Oct 6, 2026 · Official docs: Environment variables

Claude Code reads many environment variables; its documentation lists them all. One of them shows why it pays to know what’s in your environment: ANTHROPIC_API_KEY. If this variable is set, Claude Code uses that API key instead of your Claude subscription, even if you are logged in, so your usage is billed to the API account that owns the key. (In an interactive session, it asks you once to approve the key before using it.) If you meant to use your subscription, remove the variable with:

unset ANTHROPIC_API_KEY

Also delete any line that sets it from your startup file (~/.zshrc or ~/.bashrc), or it will come back in the next terminal window.

Practice Questions

Easy

Question 1 · Short answer

What is a variable in the shell?

A name that holds a value, such as course="AI Assisted Development". You read the value by putting $ in front of the name: $course.

Question 2 · Multiple choice

Which command correctly creates a variable named name with the value Maria?

  1. name = Maria
  2. name=Maria
  3. $name=Maria
  4. name == Maria

b. There must be no spaces around =. With spaces (a, d) the line is not an assignment, so you get an error instead: “command not found” for name (in zsh on a Mac, d gives the odd message zsh: = not found). The $ (c) is used only when you read a variable, not when you create it.

Question 3 · True or false

A variable you create at the prompt is still there after you close the terminal window and open a new one.

False. Variables live only as long as the shell that created them. To keep a variable, put its export line in your startup file (~/.zshrc on a Mac, ~/.bashrc in Git Bash).

Question 4 · Matching

Match each environment variable with what it holds.

Variable What it holds
1. HOME A. the folders the shell searches for commands
2. PATH B. the current working directory
3. PWD C. your home directory
4. SHELL D. the path to your default shell program

1 → C, 2 → A, 3 → B, 4 → D.

Question 5 · Short answer

What does the export command do?

It turns a shell variable into an environment variable, so that the shell passes it on to every program it starts. Without export, only the shell itself knows the variable.

Question 6 · Multiple choice

Which command lists all of your environment variables?

  1. ls
  2. env
  3. echo
  4. pwd

b. env (or printenv with no arguments) lists all environment variables. ls lists files, echo prints the text you give it, and pwd prints the current directory.

Medium

Question 7 · Predict the output

What does this print, and how would you fix it so it prints I like apples?

fruit=apple
echo "I like $fruits"

It prints I like (followed by nothing), because the shell looks for a variable named fruits, which doesn’t exist and is therefore empty. Use curly braces to mark where the name ends:

echo "I like ${fruit}s"

Question 8 · Write the command

Create a variable project with the value budget app, then use it to print Working on budget app.

project="budget app"
echo "Working on $project"

The quotes around budget app are needed because the value contains a space.

Question 9 · Predict the output

What does each printenv command print? Explain the difference.

color="blue"
printenv color
export color
printenv color

The first printenv color prints nothing; the second prints blue. printenv is a separate program, so it only sees environment variables. At first, color is only a shell variable. After export color, the shell passes it on to the programs it starts, including printenv.

Question 10 · Short answer

Maria types which python3 and sees /usr/bin/python3. What does this tell her?

When she types python3, the shell runs the program stored at /usr/bin/python3. The folder /usr/bin is on her PATH, and it’s the first folder in the PATH that contains a program named python3.

Question 11 · Scenario

Sam installs a new command-line tool, but typing its name gives command not found. Give three possible reasons.

  1. He mistyped the command name.
  2. The installation didn’t finish, so the program isn’t actually there.
  3. The program was installed in a folder that isn’t on his PATH (or he needs to open a new terminal window so that an updated PATH takes effect).

Question 12 · Write the command

Maria (Mac) wants export EDITOR="nano" to apply in every new Terminal window, and she wants it to take effect in her current window too. Write two commands that do this without opening an editor.

echo 'export EDITOR="nano"' >> ~/.zshrc
source ~/.zshrc

>> appends the line to the end of her startup file (a single > would erase the rest of the file), and source runs the file in the current shell.

Question 13 · Short answer

Give two reasons why programs usually read API keys from environment variables instead of having the keys typed into the code.

  1. Security: code is shared (for example on GitHub); a key written in the code would be visible to everyone who can see it.
  2. Flexibility: the same code can use different keys and settings on different computers (your laptop, a teammate’s laptop, the production server) without being changed.

Question 14 · Write the command

In Git Bash, echo $USER prints an empty line for Sam. Give two commands that print his user name.

echo $USERNAME (Windows stores the user name in USERNAME) or whoami.

Hard

Question 15 · Find the bug

Maria wanted to add her ~/bin folder to her PATH and typed:

export PATH="$HOME/bin"

Now even ls says command not found. What went wrong, how does she recover, and what should she have typed?

She replaced the whole PATH with a single folder, so the shell no longer looks in /bin, /usr/bin, and the other folders where ls and most other commands live. To recover, she can close the terminal window and open a new one (the change only affected that window). The correct command keeps the old list at the end:

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

Question 16 · Scenario

Sam opened ~/.bashrc in nano, added export GREETING="hi", saved, and exited. In the same window, echo $GREETING prints an empty line.

  1. Why, and what two things could he do?
  2. The next time he opens Git Bash, he sees a WARNING about ~/.bash_profile. Should he worry?
  1. Startup files are read only when a shell starts, and his current shell started before he edited the file. He can run source ~/.bashrc to run the file in the current shell, or open a new Git Bash window.

  2. No. Git Bash starts by reading ~/.bash_profile. Because he had a ~/.bashrc but no ~/.bash_profile, Git Bash created a ~/.bash_profile that loads ~/.bashrc and printed the warning to tell him so. It only happens once.

Question 17 · Scenario

A student with a Claude Pro subscription notices charges on an API account she set up last semester for another class. Her ~/.zshrc contains a line that exports ANTHROPIC_API_KEY. Explain what is probably happening and how she should fix it.

When ANTHROPIC_API_KEY is set, Claude Code uses that API key instead of her subscription (after she approved the key once in an interactive session), so her usage is billed to the API account. To fix it, she should run unset ANTHROPIC_API_KEY in her current terminal and delete the export line from ~/.zshrc (otherwise it comes back in every new window). She should not “check” the key by printing it with echo, since that shows the secret on screen.

Question 18 · Long answer

A web app reads the environment variable DATABASE_URL to decide which database to connect to. Explain how this lets the same code run safely on a developer’s laptop and on the production server.

The code never contains a database address; it just reads DATABASE_URL from its environment. On the laptop, DATABASE_URL (for example in a .env file) points to a practice database with fake data, so mistakes during development can’t damage real customer data. On the production server, the hosting provider sets DATABASE_URL to the real database. Nobody has to edit the code when it moves from one machine to the other, and the real database’s password never appears in the code or in Git.

Question 19 · Compare

After city="Boston" (without export), echo $city prints Boston, but printenv city prints nothing. Explain why.

city is a shell variable, which only the shell itself knows about. In echo $city, the shell replaces $city with Boston before echo runs, so echo just prints the text it was given. printenv is a separate program that looks the name up in its own environment, and shell variables aren’t passed to programs the shell starts. After export city, printenv city would print Boston too.

Question 20 · Ordering

Put these steps in order to permanently add ~/bin to the PATH on a Mac:

  1. Run source ~/.zshrc (or open a new Terminal window).
  2. Open the startup file with nano ~/.zshrc.
  3. Check the result with echo $PATH.
  4. Add the line export PATH="$HOME/bin:$PATH" at the bottom of the file.
  5. Save with Ctrl+O, Enter, and exit with Ctrl+X.

b → d → e → a → c. Open the file, add the line, save and exit, load the file into the current shell (or open a new window), and then check that PATH now starts with Maria’s bin folder.

© 2025-2026 Y. Rosenthal. All rights reserved.