Parenthesis Spawn a Subshell
In the following code you might think that Unhappy finish
will be triggered, since unhappy_func
, returns 1 and we have ||
right after unhappy func.
unhappy_func(){
echo "unhappy_func triggered"
return 1
}
happy_func(){
echo "happy_func triggered"
return 0
}
main() {
happy_func && (unhappy_func || return 1)
echo.green "happy finish"
}
main "${@}" || echo.red "Unhappy finish"
But the output will instead be:
happy_func triggered
unhappy_func triggered
happy finish
What in the world?!
Well. The answer lies in ()
spawning a sub-shell, meaning a new child process.
Another gotcha related to when you decide to use $$
to spot that child process. (to try to see different ProcessID) Well... you won't see a separate ID, which can make you think that it's still the same process and no sub-shells are running. Look at example below if curious.
Example usage of $$ with ()
unhappy_func(){
echo "unhappy_func triggered (ScriptPID: $$)"
return 1
}
happy_func(){
echo "happy_func triggered (ScriptPID: $$)"
return 0
}
main() {
happy_func && (unhappy_func || return 1)
echo.green "happy finish"
}
main "${@}" || echo.red "Unhappy finish"
Output:
happy_func triggered (ScriptPID: 53279)
unhappy_func triggered (ScriptPID: 53279)
happy finish
This is due to $$
giving the process id of the original shell when sub-shells are spawned using ()
$BASHPID to the rescue
In bash there is another way to spot the real process id: $BASHPID
Let's look at the usage of $BASHPID
unhappy_func(){
echo "unhappy_func triggered (BASHPID: $BASHPID)"
return 1
}
happy_func(){
echo "happy_func triggered (BASHPID: $BASHPID)"
return 0
}
main() {
echo "main PID: $$"
happy_func && (unhappy_func || return 1)
echo.green "happy finish"
}
main "${@}" || echo.red "Unhappy finish"
Output:
main PID: 54419
happy_func triggered (BASHPID: 54419)
unhappy_func triggered (BASHPID: 54421)
happy finish
Notice two things:
- BASHPID is equal to $$ while in
happy_func
- BASHPID is not equal to $$ while in
unhappy_func
Hence, we can use BASHPID to spot whether we are in a different subshell.
()
is a separate process: You env variable changes won't propagate up.
This can be good or bad. But if you modify variable within the function running within ()
they actually won't change your parent process env variable.
It can be good or bad. However, if you aren't aware that ()
is spawning a child process and expecting a parent process ENV variable to be changed from a function running within the subshell then it can be quite surprising when it doesn't change.
GT-Sandbox-Snapshot: Showing different BASHPID in ()
Code
main() {
echo "Start in main- BASHPID=$BASHPID"
(echo "In parenthesis spawned subshell- BASHPID=$BASHPID")
echo "End in main- BASHPID=$BASHPID"
}
main "${@}" || exit 1
Command to reproduce:
gt.sandbox.checkout.commit e0b3223a8e1bb518336e \
&& cd "${GT_SANDBOX_REPO}/bash" \
&& cmd.run.announce "./main.sh"
Recorded output of command:
Start in main- BASHPID=51453
In parenthesis spawned subshell- BASHPID=51454
End in main- BASHPID=51453
Example of parent ENV variable not being changed
unhappy_func(){
MY_ENV="unhappy_func"
echo "unhappy_func triggered (BASHPID: $BASHPID), MY_ENV: $MY_ENV"
return 1
}
happy_func(){
echo "happy_func triggered (BASHPID: $BASHPID), MY_ENV: $MY_ENV"
return 0
}
main() {
MY_ENV="main"
echo "main PID: $$"
happy_func && (unhappy_func || return 1)
echo "main MY_ENV: $MY_ENV"
echo.green "happy finish"
}
main "${@}" || echo.red "Unhappy finish"
main PID: 55154
happy_func triggered (BASHPID: 55154), MY_ENV: main
unhappy_func triggered (BASHPID: 55155), MY_ENV: unhappy_func
main MY_ENV: main
happy finish
What to use instead of ()
when you want to execute multiple functions after chain event like &&
or ||
So ()
should be reserved for special cases when we do in fact want to spawn a sub-shell process using some function that is available in our shell.
However, for typical cases that's not what we want to do. But what do we do when we want to group multiple functions to execute after a chaining &&
or ||
?
Well we can use {}
Use `{}` instead.
unhappy_func1(){
echo "unhappy_func1 triggered (BASHPID: $BASHPID)"
return 1
}
unhappy_func2(){
echo "unhappy_func2 triggered (BASHPID: $BASHPID)"
return 1
}
happy_func(){
echo "happy_func triggered (BASHPID: $BASHPID)"
return 0
}
main() {
echo "main PID: $$"
happy_func && {
unhappy_func1 || return 1
unhappy_func2 || return 1
}
echo.green "happy finish"
}
main "${@}" || echo.red "Unhappy finish"
Output:
main PID: 55814
happy_func triggered (BASHPID: 55814)
unhappy_func1 triggered (BASHPID: 55814)
Unhappy finish
Notice how the BASHPID stays constant. So we aren't spawning any sub shells. Notice that we never hit unhappy_func2
since we || return 1
after call to unhappy_func1
within {}.
Related
Backlinks