It's not about the parent / child relationship.
When a process forks a child, the child is an almost exact copy of the parent, so it inherits everything.
In:
$ var=1; (echo "$var"; var=2); echo "$var"
1
1
Where most shells implement the (...)
subshell environment by forking a child process, that child process has access to the same data as the parent.
The environment variables are about preserving data (in that case key=value
strings) across execution of a new command within a same process.
When you do:
$ export VAR=value
$ printenv VAR
value
The shell forks a child process (which inherits everything), but then in that child process executes the printenv
command (and with exec printenv VAR
, the forking is skipped).
It's only at that point that the memory of that child process is completely wiped before loading the new executable, so everything is lost. The environment variables are passed in the third argument of the execve("/usr/bin/printenv", ["printenv", "VAR"], environ)
system call.
The shell places the VAR=value
inside that environ
array of strings for printenv
to retrieve it, and in Bourne-like shells, only the variables that have been marked with the export
attributes are put there (with some variation in behaviour when those variables are not scalar variables).
Some shells like rc
and derivatives put all their variables in the environment even the array ones (which they need to encode some specific way which is only understood by other instances of themselves).
The Bourne shell had env vars even more separated from shell vars in that shell variables were created from env var on startup, but you had to export those shell variables even if they were initially imported from the environment for any modification made to them to be propagated to executed commands. It's similar in the C-shell, where you have to use setenv
instead of set
to set values of variables if you want the modification to be exported to executed commands.
ksh
also exports the attributes of exported variable via the special A__z
env var. bash
can also export its options to other executed bash
instances via the SHELLOPTS
and BASHOPTS
environment variables.
In perl
, the environment variables are mapped to the %ENV
associative array variable.
Note that env var names can contain any sequence of bytes other than =
and NUL, and the third argument of execve()
can contain more than one definition for a same variable and even strings that don't contain =
characters, while shells have much greater restrictions as to what their variable names may contain. You'll find some variation in behaviour as to what shells do with environment strings that can't be mapped to shell variables.
set -k
is so that one can usecmd ENVVAR=value
in place ofENVVAR=value cmd
, that won't work in your example unlessset -k
was run prior to invokingf
. Also, not many shells support it nowadays and only for backward compatibility with the Bourne shell. In the Bourne (or Korn) shell, that wouldn't work for functions. And because it affects shell parsing, it has to be in effect at the time the shell reads the code that makes use of it there. – Stéphane Chazelas Nov 07 '17 at 09:49set -a
– Stéphane Chazelas Nov 07 '17 at 09:50exec
manpage, which calls the variable external. An extern is a global. Anyway, the environment that subprocesses get is a copy of the parent process's, so if the child changes a value it doesn't affect the parent or siblings. – kojiro Feb 18 '24 at 04:49