This seems to be a bash or even POSIX bug: https://groups.google.com/forum/?fromgroups=#!topic/gnu.bash.bug/NCK_0GmIv2M
Trouble with errexit in bash
-
07-12-2021 - |
题
I'm writing a bash script and I'd like it to crash on the first error. However, I can't get it to do this in a specific circumstance I simplified below:
#!/bin/bash
set -Exu
bad_command() {
false
#exit 1
echo "NO!!"
}
(set -o pipefail; bad_command | cat ; echo "${PIPESTATUS[@]}; $?") || false
echo "NOO!!"
The expected behaviour would be a crash of the bad_command
subshell, propagated to a crash of the ()
subshell, propagated to a crash of the outter shell. But none of those crash, and both NOs get printed(!?)
If I uncomment the exit 1
statement, then the NO is no longer printed, but NOO still is(!?)
I tried using set -e
expicitly inside each of the 3 shells (first line in function, first statement after (
, but there's no change.
Note: I need to execute the pipe inside the ()
subshell, because this is a simplification of a more elaborate script. Without the ()
subshell, everything works as expected, no NOs whatsoever with either false
or exit 1
.
解决方案
其他提示
After hitting the same problem, I have found a workaround. Actually 3 depending on what you want to achieve.
First a small rewrite of the OP example code since handling the exit code requires some extra work down the line:
#! /bin/bash
set -eEu
bad_command_extra() {
return 42
}
bad_command() {
bad_command_extra
echo "NO!!"
}
if bad_command; then echo "NOO!!"; else echo "errexit worked: $?"; fi
If it's only needed to have the errexit work, following is sufficient in calling bad_command
. The trick is to launch bad_command
in the background:
(bad_command) &
bc_pid=$!
if wait $bc_pid; then echo "NOO!!"; else echo "errexit worked: $?"; fi
If you want to work with the output as well (similar to abc=$(bad_command)
), capture it in a temporary file as usual:
tmp_out=$(mktemp)
tmp_err=$(mktemp)
(bad_command >$tmp_out 2>$tmp_err) &
bc_pid=$!
if wait $bc_pid; then echo "NOO!!"; else echo "errexit worked: $?"; fi
cat $tmp_out $tmp_err
rm -f $tmp_out $tmp_err
Finally, I found out in my testings that the wait
command returned either 0 or 1 but not the actual exit code of bad_command
(bash 4.3.42). This requires some more work:
tmp_out=$(mktemp)
tmp_err=$(mktemp)
tmp_exit=$(mktemp)
echo 0 > $tmp_exit
(
get_exit () {
echo $? > $tmp_exit
}
trap get_exit ERR
bad_command >$tmp_out 2>$tmp_err
) &
bc_pid=$!
bc_exit=$(cat $tmp_exit)
if wait $bc_pid
then echo "NOO!!"
else echo "errexit worked: $bc_exit"
fi
cat $tmp_out $tmp_err
rm -f $tmp_out $tmp_err $tmp_exit
For some strange reason, putting the if
on one line as before got me exit code 0 in this case !