Issue
This works, as it should
$ bash -cx 'echo $BASH_VERSION; set -euo pipefail; if true; then (echo good reaction, bad result >&2; exit 1); else echo bad reaction; fi'
+ echo '5.2.21(2)-release'
5.2.21(2)-release
+ set -euo pipefail
+ true
+ echo good reaction, bad result
good reaction, bad result
+ exit 1
But a similar snippet using conditionals instead doesn’t. I understand why it doesn’t (although I’m not sure why the errfail setting doesn’t stop execution), but would like to know if there’s a way to make conditional execution work like an if
statement
$ bash -cx 'echo $BASH_VERSION; set -euo pipefail; true && ( (echo good reaction, bad result >&2; exit 1); ) || { echo bad reaction; }'
+ echo '5.2.21(2)-release'
5.2.21(2)-release
+ set -euo pipefail
+ true
+ echo good reaction, bad result
good reaction, bad result
+ exit 1
+ echo bad reaction
bad reaction
Solution
Assuming you're going to take the advice to not use set -euo pipefail
then I think the question is really just how to use conditionals to mimic:
if command1; then command2; else command3; fi
so that'd be the following:
command1 && { command2; es="$?"; } || { command3; es="$?"; }; (exit "$es")
That works because even if command2
fails, the subsequent es="$?"
resets the exit status to success
so that command3
doesn't get executed and then the (exit "$es")
at the end spawns a subshell just to get an exit status that's the contents of es
but, as others have mentioned, it's not worth the effort.
The above assumes you can guarantee that assigning to es
can't fail (e.g. as it could if es
was readonly
)
Here's a test script to show the equivalence of the above structure to the if...then...else
code, including verifying that for each line:
- The same commands execute.
- Variables being set at each step are retained in the current shell once the line ends.
- We have the correct exit status in the current shell at the end of the line.
- The current shell continues to execute after the line ends.
$ cat tst.sh
#!/usr/bin/env bash
# run each line in a subshell just so variables x, y, and z, all start as null for testing
( x=T; if true; then y=T; true; else z=T; true; fi; echo "$x?$y:$z -> $?" )
( x=T; if true; then y=T; true; else z=F; false; fi; echo "$x?$y:$z -> $?" )
( x=T; if true; then y=F; false; else z=T; true; fi; echo "$x?$y:$z -> $?" )
( x=T; if true; then y=F; false; else z=F; false; fi; echo "$x?$y:$z -> $?" )
( x=F; if false; then y=T; true; else z=T; true; fi; echo "$x?$y:$z -> $?" )
( x=F; if false; then y=T; true; else z=F; false; fi; echo "$x?$y:$z -> $?" )
( x=F; if false; then y=F; false; else z=T; true; fi; echo "$x?$y:$z -> $?" )
( x=F; if false; then y=F; false; else z=F; false; fi; echo "$x?$y:$z -> $?" )
printf -- '--------\n'
( { x=T; true; } && { y=T; true; es=$?; } || { z=T; true; es=$?; }; (exit "$es"); echo "$x?$y:$z -> $?" )
( { x=T; true; } && { y=T; true; es=$?; } || { z=F; false; es=$?; }; (exit "$es"); echo "$x?$y:$z -> $?" )
( { x=T; true; } && { y=F; false; es=$?; } || { z=T; true; es=$?; }; (exit "$es"); echo "$x?$y:$z -> $?" )
( { x=T; true; } && { y=F; false; es=$?; } || { z=F; false; es=$?; }; (exit "$es"); echo "$x?$y:$z -> $?" )
( { x=F; false; } && { y=T; true; es=$?; } || { z=T; true; es=$?; }; (exit "$es"); echo "$x?$y:$z -> $?" )
( { x=F; false; } && { y=T; true; es=$?; } || { z=F; false; es=$?; }; (exit "$es"); echo "$x?$y:$z -> $?" )
( { x=F; false; } && { y=F; false; es=$?; } || { z=T; true; es=$?; }; (exit "$es"); echo "$x?$y:$z -> $?" )
( { x=F; false; } && { y=F; false; es=$?; } || { z=F; false; es=$?; }; (exit "$es"); echo "$x?$y:$z -> $?" )
$ ./tst.sh
T?T: -> 0
T?T: -> 0
T?F: -> 1
T?F: -> 1
F?:T -> 0
F?:F -> 1
F?:T -> 0
F?:F -> 1
--------
T?T: -> 0
T?T: -> 0
T?F: -> 1
T?F: -> 1
F?:T -> 0
F?:F -> 1
F?:T -> 0
F?:F -> 1
Answered By - Ed Morton Answer Checked By - Katrina (WPSolving Volunteer)