Why this matters
Scripts need to remember things: a file name, a count, a directory to back up, the result of a command. In the shell, that memory is the variable. Variables are also how configuration reaches programs: PATH, HOME, LANG, EDITOR and hundreds of tool-specific settings are variables in the environment that every process inherits from its parent. Knowing exactly which variables a program can see, and why, saves hours of "but I set it!" debugging.
Assigning and using a variable
$ name=Ada
$ echo $name
Ada
$ echo "Hello, $name"
Hello, Ada
$ echo "Hello, ${name}!"
Hello, Ada!
Rules for assignment:
- The form is
name=value, written as one word with no spaces around=. - Names consist of letters, digits and underscores and do not start with a digit:
file,backup_dir,count2. They are case-sensitive:nameandNAMEare different variables. - The value is a string. bash variables have no types;
n=42stores the two characters4and2. Arithmetic, when you ask for it (lesson 5), interprets the text as a number. - To put spaces in a value, quote it:
title='My Report'ortitle="My Report".
To use a variable you write $name; bash replaces it with the value before running the command. This replacement is called parameter expansion. Braces ${name} mark exactly where the name ends, which you need when letters follow:
$ fruit=apple
$ echo "$fruits"
$ echo "${fruit}s"
apples
$fruits asked for a variable called fruits, which does not exist. An unset variable expands to nothing, with no error by default. (Module 12 shows how to make that an error.)
Change a variable by assigning again; remove it with the builtin unset:
$ name=Grace
$ echo $name
Grace
$ unset name
$ echo "[$name]"
[]
Note: Notice that
unset namehas no$. You are naming the variable, not using its value. The same goes for assignment and forexport name.$means "the value of".
Where variables live
A shell variable lives inside the memory of one bash process. Nothing else can see it. That is fine as long as you stay in that shell, but every external program is a separate process, as you learned with fork and exec. So how does ls know your LANG, or how does man know which pager to use?
The answer is the environment: a list of NAME=value strings that the kernel hands to every new program when it is exec'd. Every process has one. When bash forks a child and execs a program, it builds the environment from the variables that are marked for export.
bash process memory
┌──────────────────────────────────────┐
│ shell variables: city=Paris │ not exported: stays here
│ count=3 │
│ exported: HOME=/home/learner │──┐
│ PATH=/usr/bin:... │ │ copied into the environment
│ country=France │ │ of every program bash starts
└──────────────────────────────────────┘ │
▼
execve("/usr/bin/env", args, ["HOME=...", "PATH=...", "country=France"])
So there are two kinds of variable:
- Shell variables: known only to this shell.
- Environment variables: shell variables marked for export, copied into children.
export marks a variable; env (or printenv) prints the environment that a child would see:
$ city=Paris
$ export country=France
$ env | grep -e city -e country
country=France
$ printenv country
France
$ printenv city
$ bash -c 'echo "city=$city country=$country"'
city= country=France
bash -c 'some code' starts a new bash process that runs the code. It is a handy way to see what a child receives. The single quotes matter here: they stop your shell from expanding the variables, so the child does the expanding with what it was given.
The variables you start with (HOME, PATH, LANG, TERM) are already exported: your shell inherited them from whatever started it (here, the container runtime) and passes them on.
Copies, not shared memory
The child receives a copy. If it changes or exports variables, those changes die with it. There is no way for a child process to change its parent's variables:
$ export mood=calm
$ bash -c 'mood=angry; echo "child: $mood"'
child: angry
$ echo "parent: $mood"
parent: calm
This is the same reason as cd being a builtin, and it is why a script that sets variables does not change your interactive shell when you run it as a program. (You will learn source, which runs a script inside the current shell, in the next module.)
Setting a variable for one command
You can put assignments in front of a command. They go into that command's environment only, and the shell's own variables are untouched:
$ LANG=C date
Sun Nov 1 14:03:12 UTC 2026
$ TZ=America/New_York date
Sun Nov 1 09:03:12 EST 2026
$ echo "[$TZ]"
[]
TZ tells time functions which time zone to use. This prefix form is common in scripts: LC_ALL=C sort file to sort by raw bytes, EDITOR=nano crontab -e, and so on.
Special and well-known variables
Some variables are set and used by bash itself:
| Variable | Meaning |
|---|---|
HOME | your home directory; cd with no argument goes there |
PATH | where to look for commands (lesson sh-01-02) |
PWD, OLDPWD | current and previous directory (cd - uses OLDPWD) |
PS1 | the prompt string |
SHELL | your login shell (not necessarily the one running) |
RANDOM | a new random number 0-32767 each time you read it |
$?, $$ | last exit status, PID of the shell (special parameters, no name) |
Try echo $RANDOM $RANDOM and echo $PWD.
A naming convention keeps you out of trouble: environment and bash's own variables are UPPERCASE, so use lowercase for your own script variables. If you write PATH=/backup in a script meaning "the backup path", every command after that is "not found".
set with no arguments prints every shell variable and function (a lot of output; pipe it into less). declare -p name prints one variable in a form you could paste back:
$ declare -p country
declare -x country="France"
The -x means exported.
Common mistakes
- Spaces around
=.count = 3givesbash: count: command not found. $on the left side.$count=3expands$countfirst; if it is empty, bash tries to run=3and saysbash: =3: command not found.- Forgetting
export. The variable works in your shell, but the program you start does not see it. Check withprintenv name. - Expecting a script to change your shell's variables. It runs in a child; the changes vanish.
- Overwriting
PATHor other uppercase variables by accident. Use lowercase names. - Name running into text.
"$file_backup"is the variablefile_backup; write"${file}_backup".
Exercises
The quizzes test the fork/exec model of the environment, which you will lean on in every script you write. The task uses your lesson shell; the code exercise runs as bash script.sh.
Recap
name=value(no spaces) assigns;$nameor${name}expands;unset nameremoves.- Values are strings; unset variables expand to nothing.
- Shell variables live in one process.
exportputs a variable in the environment, which is copied to every program the shell starts. Children get copies and cannot change the parent. VAR=value commandsets a variable for one command only.env,printenv NAMEanddeclare -p NAMEshow what is set. Use lowercase for your own names.
Going further: run env | sort | less and look up two variables you do not recognize. Then look at /proc/self/environ: tr '\0' '\n' < /proc/self/environ. That file is the environment the kernel gave to the tr process itself, NUL-separated exactly as execve received it.