Из-за того, как работает set -o pipefail
, вы можете захватить stderr в трубе и выйти с ненулевым статусом выхода из трубы, если на stderr есть что-то. Таким образом, в основном pipefail
выполняет двоичное состояние и состояние входа или выхода - если команда завершается с ненулевым состоянием выхода, pipefail
завершает работу канала с этим состоянием, если команда завершается с нулевым состоянием выхода, то с правой стороны канала завершится неудачно с ненулевым состоянием выхода, поэтому вся труба выйдет с ненулевым состоянием выхода.
# Usage: exit_if_nonzero_or_stderr command [args...]
# Executes the command [args...] and
# exits with nonzero exit status
# if the command exits with nonzero exit status
# __or__
# the command outputs anything on stderr.
exit_if_nonzero_or_stderr() {
(
set -o pipefail
{ "$@" 1>&3 ;} 2>&1 | {
if IFS= read -r line; then
printf "%s\n" "$line"
cat
exit 1
fi
} >&2
) 3>&1
}
Проверено для всех комбинаций:
tester() {
for i in \
'echo 1; true;' \
'echo 1; false;' \
'echo 1; echo 2 >&2; true;' \
'echo 1; echo 2 >&2; false;'
do
eval "f() { $i }"
set -o pipefail
exit_if_nonzero_or_stderr f 2> >( \
sed 's/^/stderr: /' >&2) |
sed 's/^/stdout: /';
echo "f() { $i } -> exit status: $?";
done
}
tester
Вывод tester
:
stdout: 1
f() { echo 1; true; } -> exit status: 0
stdout: 1
f() { echo 1; false; } -> exit status: 1
stderr: 2
stdout: 1
f() { echo 1; echo 2 >&2; true; } -> exit status: 1
stdout: 1
stderr: 2
f() { echo 1; echo 2 >&2; false; } -> exit status: 1
Альтернативная реализация и немного больше многословия и больше памяти, но с большим количеством posix-i sh вы можете получить stderr
переменную и проверить, если она ненулевая:
exit_if_nonzero_or_stderr2() {
local stderr ret
# redirect stdout to output with temporary file descriptor
# and grab stderr
{ stderr=$({ "$@" 1>&3 ;} 2>&1 ) ;} 3>&1
# remember return value
ret=$?
# if theres anything on stderr, output it
if [[ -n "$stderr" ]]; then
cat <<<"$stderr" >&2
# if stderr, always nonzero
return 1
fi
# else we return with the exit status of the command
return "$ret"
}