Environment & the Shell: Variables, export & What Programs Inherit
- Read a variable with $NAME and set one with NAME=value (no spaces around =)
- Explain the difference between a shell variable and an environment variable, and why only exported variables reach child processes
- Inspect the environment with env / printenv and recognize the common variables (HOME, PATH, USER, SHELL, LANG)
- Make a variable last: for one command (VAR=x cmd), one session, or permanently via a startup file
The environment is a set of name=value pairs
See that the shell — and every program it launches — carries a bag of named values it can read.
Alongside the commands you run, your shell carries a set of variables — named values it can read and pass along. Some you'll recognize already:
HOMEis your home directory,USERis your username,PATHis the command-search list from the How the Shell Finds a Command lesson. Together the exported ones form the environment: a bag ofname=valuepairs that every program the shell launches inherits a copy of.You read a variable by putting
$in front of its name —$HOME— and the shell expands it to the value before the command runs (that's the same expansion you met with quoting). You set one withNAME=value. The single most common beginner trip-up is spacing: it must beNAME=valuewith no spaces around the=.NAME = valueis read as the commandNAMEwith arguments=andvalue.echo "$HOME — $USER — $SHELL"/home/ada — ada — /bin/bash
Three variables the shell set up for you, read with $ and expanded before echo runs. You didn't define these — your login shell did. Note the double quotes: they let the variables expand while keeping the spaced text as one argument (straight from the quoting lesson). Shell variable vs environment variable: export is the bridge
Understand why a plain variable stays local and export is what lets child processes see it.
Here is the distinction that explains almost every "but I set that variable!" confusion:
- `NAME=value` creates a shell variable — visible only to this shell. The programs you launch do not see it.
- `export NAME` (or
export NAME=value) promotes it to an environment variable — now it's part of the environment that every child process inherits.
Why the split? When the shell starts a program, the child gets a copy of the exported variables only. A plain shell variable is the shell's private scratch space; exporting is you saying "children should get this too." This is exactly why
PATHis exported — every program you run needs it — and why a variable you set for yourself at the prompt doesn't leak into unrelated programs unless you ask.GREETING=hello; bash -c 'echo "child sees: [$GREETING]"'child sees: []
GREETING is a plain shell variable, so when we launch a child shell (bash -c) it inherits nothing — the child prints an empty value. The parent set it; the child never received it, because it was never exported. export GREETING; bash -c 'echo "child sees: [$GREETING]"'child sees: [hello]
Now export puts GREETING into the environment, so the child process inherits a copy and prints it. Same variable, same value — the only change is that it's now exported, which is the whole difference between 'the shell knows it' and 'programs I run know it.' Check yourself
You set DB=prod then run a script that reads $DB, but the script sees nothing. What did you forget?
Seeing the environment: env and printenv
List the current environment and recognize the variables that matter.
Two tools show you the environment as it stands:
- `printenv` — print all environment variables, or one by name:
printenv PATH. - `env` — the same listing, and it can also run a command with a modified environment (more on that next).
The variables worth knowing on sight:
- `HOME` your home dir · `USER` your name · `PWD` the current dir · `SHELL` your login shell
- `PATH` the command search list · `LANG`/
LC_*language & formatting · `EDITOR` the editor tools launch (e.g.git,crontab)
Plain shell variables won't appear here —
printenvshows only what's been exported, which is a handy way to check whether something is in the environment or just a local shell variable.printenv HOME PATH LANG/home/ada /usr/local/bin:/usr/bin:/bin en_US.UTF-8
printenv fetches specific environment variables by name — one per line. If you asked printenv for a plain (unexported) shell variable, you'd get nothing back and a non-zero exit status, which is itself the answer: 'that's not in the environment.' - `printenv` — print all environment variables, or one by name:
How long a variable lasts — and making it permanent
Scope a variable to one command, one session, or every future session.
A variable's lifetime depends on where you set it:
- One command — put the assignment in front of the command:
EDITOR=nano crontab -e. It's exported to that one child and gone afterward — perfect for a one-off override without changing anything permanently. - One session — set (and
export) it at the prompt. It lives until you close that shell; a new terminal starts fresh, because each shell gets its own copy of the environment at launch. - Every future session — put the
exportline in a shell startup file that runs when a shell begins:
- `~/.bashrc` runs for interactive non-login shells (a new terminal/tab). This is where most people add
export PATH=...andexport EDITOR=....- `~/.bash_profile` / `~/.profile` run for login shells (an SSH login, a console login).
- Every user, system-wide —
/etc/profileruns for every user's login shell, and so does any*.shfile dropped into `/etc/profile.d/`. A drop-in like/etc/profile.d/tools.shholdingexport TOOLS_HOME=/opt/toolsandexport PATH="$PATH:/opt/tools/bin"is the clean way to set something for everyone, without editing/etc/profileitself.
Because these files are just read at startup, editing them doesn't change shells that are already open — you either open a new shell or re-read the file with
source ~/.bashrc.EDITOR=nano bash -c 'echo "this child EDITOR = $EDITOR"'; echo "my shell EDITOR = ${EDITOR:-unset}"this child EDITOR = nano my shell EDITOR = unset
The inline assignment exports EDITOR to just that one child (which sees 'nano'), then vanishes — your own shell's EDITOR is still unset. This is the cleanest way to influence a single command's behavior without leaving a setting behind. (${EDITOR:-unset} prints 'unset' when the variable is empty — a handy default.) grep -h 'export ' ~/.bashrc 2>/dev/null | head -3; echo '(these run every time a new shell starts)'Peek at the export lines already in your ~/.bashrc — the settings that get re-applied every time you open a terminal. Adding an 'export NAME=value' line here (then opening a new shell or running 'source ~/.bashrc') is how a variable becomes permanent. Your terminal supplies the actual output. Startup files hold one more thing you'll want everywhere: aliases.
alias ll='ls -alF'makeslla shortcut the shell expands before running the command. An alias is not a variable, soexportdoesn't apply: it lives only in the shell that defined it, and no child process or script ever sees it. The way to have it in every shell is the same as for a permanent variable: put thealiasline in~/.bashrc, or for every user in a/etc/profile.d/*.shdrop-in.alias ll='ls -alF'; alias ll; bash -c 'alias ll' 2>&1alias ll='ls -alF' bash: line 1: alias: ll: not found
- alias NAME
- print that alias's definition (bare
aliaslists them all)
Define the alias, then ask about it twice. Your shell knows it and prints the definition. The child bash doesn't: aliases are never inherited, exported or not, so the child reports 'not found'. Each new shell gets its aliases only by reading a startup file. Check yourself
You want a PATH addition to apply in every new terminal from now on. Where does the export line go?
- One command — put the assignment in front of the command:
Now do it — watch a value cross into a program
Prove to yourself that export is the boundary between the shell and the programs it runs.
One continuous experiment that makes the shell/environment boundary visible. Set a variable, try to pass it to a child, then export and try again.
TOKEN=abc123; bash -c 'echo "child: [$TOKEN]"'; export TOKEN; bash -c 'echo "child: [$TOKEN]"'Same child command, run twice. Before export it prints 'child: []' — the child inherited nothing. After export it prints 'child: [abc123]'. You just watched export move a value across the boundary from 'the shell knows it' to 'programs I launch know it'. That boundary is the entire concept. printenv | wc -l; printenv | grep -E '^(HOME|PATH|USER|SHELL)='Count how many variables are in your environment, then pull out the four you'll use most. Every one of these was exported by your login shell and is inherited by everything you run — which is why programs 'just know' your home directory and where to find commands. LANG=C sort <<< $'banana\nApple\ncherry'; echo '--- vs your locale ---'; sort <<< $'banana\nApple\ncherry'A real example of the environment changing behavior: LANG=C (inline, one command) makes sort use byte order, so uppercase 'Apple' sorts before lowercase. Your normal locale may sort case-insensitively. Same command, different environment, different result — that's why the environment matters, not just PATH.
- Read a variable with $NAME; set one with NAME=value — and never put spaces around the = (that would look like a command).
- A plain assignment makes a shell variable only this shell sees; export promotes it to an environment variable that every child process inherits. That's why PATH is exported.
- printenv / env list the environment (exported variables only); printenv NAME checks whether one is actually exported. Know HOME, PATH, USER, SHELL, LANG.
- Scope by where you set it: VAR=x cmd (one command), export at the prompt (one session), or an export line in ~/.bashrc / ~/.profile (every future shell — re-read with source).
- System-wide settings go in a /etc/profile.d/*.sh drop-in (read by every login shell). Aliases are not variables and are never inherited, so they too become permanent only by living in ~/.bashrc or /etc/profile.d.
| You write | Effect |
|---|---|
| NAME=value | set a shell variable (this shell only) |
| export NAME | promote it to the environment (children inherit it) |
| echo "$NAME" | read/expand the value |
| printenv / env | list exported (environment) variables |
| VAR=x cmd | set VAR for just that one command |
| export line in ~/.bashrc | apply to every new interactive shell |
