Signal9Signal9 — home
online
all lessons

Environment & the Shell: Variables, export & What Programs Inherit

Beginner25 min5 steps
What you'll learn
  • 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
  1. 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: HOME is your home directory, USER is your username, PATH is the command-search list from the How the Shell Finds a Command lesson. Together the exported ones form the environment: a bag of name=value pairs 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 with NAME=value. The single most common beginner trip-up is spacing: it must be NAME=value with no spaces around the =. NAME = value is read as the command NAME with arguments = and value.

    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).
  2. 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 PATH is 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?

  3. 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 — printenv shows 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.'
  4. 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 export line 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=... and export EDITOR=....

    - `~/.bash_profile` / `~/.profile` run for login shells (an SSH login, a console login).

    • Every user, system-wide — /etc/profile runs for every user's login shell, and so does any *.sh file dropped into `/etc/profile.d/`. A drop-in like /etc/profile.d/tools.sh holding export TOOLS_HOME=/opt/tools and export PATH="$PATH:/opt/tools/bin" is the clean way to set something for everyone, without editing /etc/profile itself.

    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' makes ll a shortcut the shell expands before running the command. An alias is not a variable, so export doesn'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 the alias line in ~/.bashrc, or for every user in a /etc/profile.d/*.sh drop-in.

    alias ll='ls -alF'; alias ll; bash -c 'alias ll' 2>&1
    alias ll='ls -alF'
    bash: line 1: alias: ll: not found
    alias NAME
    print that alias's definition (bare alias lists 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?

  5. 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.
Recap
  • 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.
Environment variables at a glance
You writeEffect
NAME=valueset a shell variable (this shell only)
export NAMEpromote it to the environment (children inherit it)
echo "$NAME"read/expand the value
printenv / envlist exported (environment) variables
VAR=x cmdset VAR for just that one command
export line in ~/.bashrcapply to every new interactive shell