A bash script sets GREETING=hello and then runs ./child.sh, but child.sh prints an empty value for $GREETING. Why doesn't the child see it, and what makes a variable visible to child processes?
answer
- two stores, not one
- the child gets a copy
- attribute on the name, not the value
- export -p versus plain set
- assignments alone stay in this shell
basics
~20 sOnly exported variables become part of a child process's environment. A plain assignment creates a shell variable that lives in the current shell alone; marking it with export copies it into every command the shell runs afterwards, including child.sh.
solid answer
~40 sBash keeps two separate things. Shell variables live inside the running shell only. The *environment* is the list of `NAME=value` pairs that gets handed to every program the shell starts. A plain `GREETING=hello` creates a shell variable and nothing more, so `./child.sh` — a different process, running its own bash — starts with no `GREETING` at all. `export GREETING`, or `export GREETING=hello`, sets the export attribute on the name, and from that point bash copies it into the environment of every command it runs. The copy is one-way: the child can change its own copy freely and the parent never sees the change. If you need it for exactly one command, put the assignment in front of it: `GREETING=hello ./child.sh`.
code
bash · 8 linesGREETING=hello
bash -c 'echo "child sees: [$GREETING]"' # child sees: []
export GREETING
bash -c 'echo "child sees: [$GREETING]"' # child sees: [hello]
GREETING=bye bash -c 'echo "one-shot: [$GREETING]"' # one-shot: [bye]
echo "parent still: [$GREETING]" # parent still: [hello]go deeper
Know that a plain assignment stays inside the current shell and that export is what makes a name reach child processes. Say plainly that the child receives a copy.
Explain that export sets an attribute on the name rather than copying a value, show the one-shot assignment-prefix form, and describe how set -a turns a plain config file into exported environment.
Show the diagnosis path when a tool cannot see a credential or a locale: printenv and declare -p to prove the export attribute, and awareness that exported values are inherited by every descendant and readable from the process environment.
Own the policy question of what belongs in the environment at all. Environment variables are inherited everywhere, are hard to scope, and leak into child processes and crash dumps, so argue for passing secrets by file descriptor or a secrets client instead.
## Two different stores A bash process holds a table of **shell variables**: every name you assign to, plus the ones bash maintains itself. It also holds an **environment**, which is a plain list of `NAME=value` strings. When bash starts a program, the environment — not the shell-variable table — is what gets passed to that new process. A variable joins the environment only if it carries the *export attribute*. This is why the two look identical inside one script and diverge the moment another process is involved: ```bash GREETING=hello # shell variable, no export attribute echo "$GREETING" # hello — same shell, so of course it works bash -c 'echo "[$GREETING]"' # [] — a different process, empty ``` ## What export actually does `export GREETING` does not copy a value anywhere. It flips a flag on the name. From then on, whenever bash launches an external command, it builds that command's environment from every variable currently carrying the flag. `export GREETING=hello` is just the assignment and the flag in one statement. `declare -x GREETING` is the same thing spelled differently. The flag can be removed with `export -n GREETING`, which keeps the value as an ordinary shell variable but stops passing it on. `unset GREETING` removes the variable entirely. To see what is currently exported, use `export -p` or `declare -x` (both print exported names with their values) or `printenv` / `env` (both print the raw environment). Plain `set` prints *all* shell variables and functions, exported or not — which is why `set | grep GREETING` will happily show you a variable the child will never receive. ## The copy is one-way and per-launch Each child gets its own snapshot, taken at launch time: - Changes the parent makes **after** the child started do not reach the running child. There is no shared memory here, just a copy handed over at process creation. - Changes the child makes never travel back up. A script cannot export a variable into the shell that ran it. This is why tools that need to set variables in your shell tell you to `source` them, or to run something like `eval "$(tool env)"`, rather than just executing them. That one-way rule is the single most common source of confusion. "I set it in the sub-script and the parent still shows the old value" is not a bug; it is process isolation working as designed. ## The one-shot form Putting assignments in front of a command builds a temporary environment for that command only: ```bash GREETING=hello ./child.sh # child.sh sees GREETING=hello echo "${GREETING-unset}" # unset — the shell's own table was not touched ``` This is the right tool when you want to influence one invocation — a locale for one `sort`, a timezone for one `date` — without leaking the setting into the rest of the script. ## Where it bites in real scripts - **A variable set before a tool runs, but not exported.** `API_TOKEN=abc` then `curl` — curl reads its own environment, sees nothing, and you get a 401 that looks like a credential problem. - **Assuming `set -a` is on.** `set -a` (allexport) marks *every* subsequent assignment for export, which is how `set -a; . ./config.env; set +a` turns a plain `KEY=value` file into environment for children. Without it, sourcing that file only populates shell variables. - **Expecting a child to write back.** A wrapper script that computes a value and assigns it cannot hand that value to its caller through a variable; it has to print it, and the caller has to capture the output. - **Exporting too much.** Everything exported is visible in the child's `/proc/<pid>/environ` and in `ps -e` output on some systems, and it is inherited by *every* descendant. Secrets carried this way spread further than people expect. ## Quick diagnosis When a child is not seeing a value, check which side is missing it. `printenv NAME` inside the parent tells you whether it is in the environment at all; `declare -p NAME` shows the value and the attributes, where a leading `declare -x` confirms the export flag is set.
- A setup script assigns a computed value and the caller still sees the old one. How do you get that value back to the parent?You cannot — the child's environment is a copy, and nothing propagates upward. Either print the value and have the caller capture it with command substitution, or have the caller `source` the script so the assignments run in its own shell instead of in a separate process.
- You have a config file full of plain KEY=value lines and want them all exported. What is the idiomatic way?Wrap the sourcing in `set -a` / `set +a`: `set -a; . ./config.env; set +a`. While allexport is on, every assignment bash performs also gets the export attribute, so plain lines in the file end up in the environment. Turn it off straight after so the rest of the script is not accidentally exporting everything.
- Does exporting a variable make it visible to a process that is already running?No. The environment is handed over once, when the process is created. An already-running child holds the snapshot it was given at launch; later exports in the parent affect only commands started after that point. Changing a running process's environment requires cooperation from that process, not shell-level exporting.
saying these in an interview costs you the question
- Thinks any assignment is automatically visible to child processes
- Believes a child script can export a variable back to its parent
- Confuses set output with the actual exported environment
- Says export copies the current value, so later changes are not passed on
- Assumes sourcing and executing a script are interchangeable here