I'm confused over whether bash
variables are exported to subshells and when they are accessible by scripts. My experience so far led me to believe that bash variables are automatically available to subshells. E.g.:
> FOO=bar
> echo $FOO
bar
> (echo $FOO)
bar
The above appears to demonstrate that bash
variables are accessible in subshells.
Given this script:
#! /usr/bin/bash
# c.sh
func()
{
echo before
echo ${FOO}
echo after
}
func
I understand that calling the script in the current shell context gives it access to the current shell's variables:
> . ./c.sh
before
bar
after
If I were to call the script without the "dot space" precedent...
> ./c.sh
before
after
...isn't it the case that the script is called in a subshell? If so, and it's also true that the current shell's variables are available to subshells (as I inferred from the firstmost code-block), why is $FOO
not available to c.sh
when run this way?
Similarly, why is $FOO
also unavailable when c.sh
is run within parentheses - which I understood to mean running the expression in a subshell:
> (./c.sh)
before
after
(If this doesn't muddy this post with too many questions: if "./c.sh
" and "(./c.sh)
" both run the script in a subshell of the current shell, what's the difference between the two ways of calling?)
To make a variable available in a subshell (or any other subprogram of the shell), we have to “export” the variable with the export command. Try the previous serie of commands again, but do export fruit before starting the subshell.
To export the variable in bash, open Terminal from Menu on the bottom left on the screen in your computer system. Click on the Terminal option. Once the terminal is opened, you need to provide a variable, let's call it vech for the sake of ease. We will then assign it a value for now, i.e., “Bus”.
Export is a built-in command of the Bash shell. It is used to mark variables and functions to be passed to child processes. Basically, a variable will be included in child process environments without affecting other environments.
export makes the variable available to sub-processes. means that the variable name is available to any process you run from that shell process. If you want a process to make use of this variable, use export , and run the process from that shell.
(...)
runs ...
in a separate environment, something most easily achieved (and implemented in bash, dash, and most other POSIX-y shells) using a subshell -- which is to say, a child created by fork()
ing the old shell, but not calling any execv
-family function. Thus, the entire in-memory state of the parent is duplicated, including non-exported shell variables. And for a subshell, this is precisely what you typically want: just a copy of the parent shell's process image, not replaced with a new executable image and thus keeping all its state in place.
Consider (. shell-library.bash; function-from-that-library "$preexisting_non_exported_variable")
as an example: Because of the parens it fork()
s a subshell, but it then sources the contents of shell-library.bash
directly inside that shell, without replacing the shell interpreter created by that fork()
with a separate executable. This means that function-from-that-library
can see non-exported functions and variables from the parent shell (which it couldn't if it were execve()
'd), and is a bit faster to start up (since it doesn't need to link, load, and otherwise initialize a new shell interpreter as happens during execve()
operation); but also that changes it makes to in-memory state, shell configuration, and process attributes like working directory won't modify the parent interpreter that called it (as would be the case if there were no subshell and it weren't fork()
'd), so the parent shell is protected from having configuration changes made by the library that could modify its later operation.
./other-script
, by contrast, runs other-script
as a completely separate executable; it does not retain non-exported variables after the child shell (which is not a subshell!) has been invoked. This works as follows:
fork()
to create a child. At this point in time, the child still has even non-exported variable state copied../other-script >>log.out
, the child would open("log.out", O_APPEND)
and then fdup()
the descriptor over to 1
, overwriting stdout).execv("./other-script", {"./other-script", NULL})
, instructing the operating system to replace it with a new instance of other-script
. After this call succeeds, the process running under the child's PID is an entirely new program, and only export
ed variables survive.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With