skip to content

In bash, what does the leading assignment in the command `LC_ALL=C sort names.txt` do, and how is it different from putting `LC_ALL=C` on its own line before the sort?

level: middleimportance: should knowfreq 44%

answer

  1. one command, one environment
  2. a bare assignment exports nothing
  3. expansion happens before the assignment
  4. export widens what the prefix keeps narrow
  5. env does it from outside the shell

basics

~20 s

An assignment written in front of a command puts that name into the environment of that one command only, leaving the shell's own variables untouched. On its own line it creates an ordinary shell variable, which is not exported, so sort never sees it at all.

solid answer

~40 s

The prefix form builds a *temporary environment* for a single command. `LC_ALL=C sort names.txt` runs `sort` with `LC_ALL=C` in its environment, and once `sort` exits the shell's own `LC_ALL` is exactly what it was before — usually unset. On its own line, `LC_ALL=C` creates a plain shell variable with no export attribute, so the following `sort` is a separate process that inherits nothing and quietly sorts in the ambient locale. Those two lines look equivalent and behave completely differently. To get the same effect from a separate line you have to export: `export LC_ALL=C`, which then leaks into every later command in the script too. The prefix is the precise tool — one setting, one command, no spillover.

code

bash · 7 lines
bash
FOO=outer
FOO=inner printenv FOO      # inner  — the temporary environment
echo "$FOO"                 # outer  — the shell's variable is untouched
FOO=inner echo "$FOO"       # outer  — expanded before the assignment applies

BAR=plain                   # shell variable, never exported
bash -c 'echo "[${BAR-unset}]"'   # [unset]

go deeper

for a junior

Know that writing NAME=value in front of a command sets it for that command only, and that the same assignment on its own line does not reach any external program unless you export it.

for a middle

Explain the temporary environment precisely, and be ready for the expansion-order trick where the variable on the same line expands to its old value because bash builds the command line before applying the assignment.

for a senior

Show judgment about scope: prefix for one-off settings, a small exported set near the top for genuinely shared values, and env -i to reproduce a minimal environment and expose undeclared dependencies before a script meets an unattended runner.

for a principal

Treat the environment as an implicit interface with no schema. Argue for scripts that declare and validate the few variables they need rather than inheriting ambient state, since inherited configuration is what makes a script behave differently on a laptop and on a build agent.

## The temporary environment Bash lets any simple command be preceded by one or more `NAME=value` assignments: ```bash LC_ALL=C sort names.txt TZ=UTC date API_TOKEN=$tok curl -sf https://example.invalid/api ``` Those assignments are added to the environment handed to that command and to nothing else. The shell's own variable table is not modified, so nothing leaks into the lines that follow. It is the sharpest scoping tool bash offers for environment: narrower than `export`, and it cannot be forgotten about later. ## Why the separate line silently fails ```bash LC_ALL=C sort names.txt # sorts in whatever locale was already in the environment ``` The first line creates a shell variable. Shell variables are not part of the environment unless they are exported, and `sort` is a separate program that reads only its environment. So the setting has no effect at all — and, worse, it has no effect *silently*: the sort succeeds, it just produces a different order. Locale-sensitive sorting, `date` output and numeric formatting are exactly the places where a wrong-but-successful result does the most damage. The fix on a separate line is `export LC_ALL=C`, but note what you have bought: the value is now in the environment of every command the script runs from that point, including things you did not intend to affect. ## The expansion-order trap The prefix affects the command's *environment*, not the shell's expansions on that same line — and expansions happen first: ```bash FOO=outer FOO=inner echo "$FOO" # prints outer FOO=inner printenv FOO # prints inner ``` Bash expands `"$FOO"` while building the command line, before the temporary assignment is applied, so `echo` receives the old value as an argument. `printenv` reads the environment at runtime and sees the new one. This is a favourite interview trick and a real source of confusion in scripts that try to use the variable they just set on the same line. ## Scope details worth knowing - **Multiple assignments are allowed** and are applied left to right: `TZ=UTC LC_ALL=C date`. - **The value is expanded by the current shell**, so `PATH=/opt/bin:$PATH mytool` builds the new value from the shell's current `PATH` and hands the result to `mytool` — without changing the shell's `PATH`. - **It works for external commands and, most usefully, is the safe form for them.** When the command word is a shell function or a special builtin, the persistence rules differ and depend on POSIX mode and the bash version, so do not build logic on assignments prefixed to those — use an explicit `export` inside a subshell if you need it. - **`env` does the same thing from outside bash**: `env LC_ALL=C sort names.txt` is equivalent and is what you reach for when the command word would otherwise be ambiguous, or when you are writing for a shell that lacks the prefix form. ## Scrubbing instead of adding The complement of adding one variable is removing all of them. `env -i cmd` runs `cmd` with an *empty* environment, and `env -u NAME cmd` removes a single one. `env -i` is the practical way to reproduce the stripped-down environment that a script gets when something other than your interactive session starts it — you set only the variables you are willing to depend on and see whether the script still works: ```bash env -i PATH=/usr/bin:/bin HOME="$HOME" ./myscript.sh ``` If the script only passes with your full environment, it has an undeclared dependency on something you happen to have set. ## Choosing between the three forms - **Prefix** (`VAR=x cmd`) — one command needs it. Default choice; no cleanup, no leakage. - **`export VAR=x`** — many commands in this script need it, and you accept that every descendant gets it too. - **`env -i` / `env -u`** — you want to *reduce* what a command inherits, usually to reproduce a minimal environment or to keep a secret out of a child. A script that reads clearly tends to use the prefix for the exceptional case and export sparingly, near the top, for the handful of values genuinely shared across the whole run.

  • Why does FOO=1 echo "$FOO" print the old value rather than 1?
    Because bash expands `"$FOO"` while building the command line, and only then applies the temporary assignment to the environment it hands over. `echo` receives the old value as a literal argument. A command that reads the variable at runtime, such as `printenv FOO`, sees the new value — the difference is expansion time versus execution time.
  • How would you run a script with almost nothing in its environment, to see what it really depends on?
    `env -i` starts the command with an empty environment, so you add back only what you are willing to declare as a dependency: `env -i PATH=/usr/bin:/bin HOME="$HOME" ./myscript.sh`. Anything that then breaks was relying on a value your interactive session happened to provide. `env -u NAME` is the surgical version, removing one variable.
  • Is PATH=/opt/bin:$PATH mytool safe, given it references PATH while setting it?
    Yes. The value is expanded by the current shell using its own current `PATH`, and the result is placed only in `mytool`'s environment. The shell's `PATH` is unchanged afterwards, so later commands resolve exactly as before. It is a clean way to prepend a directory for one invocation.

saying these in an interview costs you the question

  • Thinks a bare assignment is visible to the command on the next line
  • Expects VAR=x cmd to leave VAR set in the shell afterwards
  • Believes FOO=1 echo $FOO prints 1
  • Uses export for something only one command needs
  • Confuses env with export as if they did the same job

context