捕获错误代码在壳管(Catching error codes in a shell pipe)

2019-07-22 01:29发布

我现在有一个脚本,它有点像

./a | ./b | ./c

我想修改它,如果任何一个错误代码A,B或C出口的我打印错误信息并停止,而不是正向管道坏输出。

什么是这样做的最简单/干净的方式?

Answer 1:

如果你真的不想要第二个命令继续,直到第一个被称为是成功的,那么你可能需要使用临时文件。 那简单的版本是:

tmp=${TMPDIR:-/tmp}/mine.$$
if ./a > $tmp.1
then
    if ./b <$tmp.1 >$tmp.2
    then
        if ./c <$tmp.2
        then : OK
        else echo "./c failed" 1>&2
        fi
    else echo "./b failed" 1>&2
    fi
else echo "./a failed" 1>&2
fi
rm -f $tmp.[12]

的 '1>&2' 重定向也可缩写为 '>&2'; 然而,旧版本的MKS外壳的处理不当,而不前面的“1”的错误重定向,我用的是针对年龄可靠性明确的符号。

如果中断这个东西泄露的文件。 防弹(或多或少)shell编程使用:

tmp=${TMPDIR:-/tmp}/mine.$$
trap 'rm -f $tmp.[12]; exit 1' 0 1 2 3 13 15
...if statement as before...
rm -f $tmp.[12]
trap 0 1 2 3 13 15

第一捕集线表示“运行的命令” rm -f $tmp.[12]; exit 1 rm -f $tmp.[12]; exit 1 ',当任何的信号的1个SIGHUP,2 SIGINT,3 SIGQUIT,13 SIGPIPE,或15 SIGTERM发生,或者0(当出于任何原因,外壳退出)。 如果你正在写一个shell脚本,最后的陷阱只需要去掉0的陷阱,这是shell退出陷阱(你可以保留其他信号的地方,因为这个过程大概要终止)。

在原来的管道,它是“c”的可行之前被读取来自“B”数据“A”已完成 - 这通常是可取的(它给多个核的工作要做,例如)。 如果“B”是一个“排序”的阶段,那么这将不适用 - “B”已经看不到它的所有输入之前它可以生成任何输出。

如果要检测其指令(S)失败了,你可以使用:

(./a || echo "./a exited with $?" 1>&2) |
(./b || echo "./b exited with $?" 1>&2) |
(./c || echo "./c exited with $?" 1>&2)

这是简单的和对称的 - 它是微不足道的延伸到4部分或N-部分的管道。

与“设定-e”简单的实验并没有帮助。



Answer 2:

bash您可以使用set -eset -o pipefail你的文件的开头。 后续命令./a | ./b | ./c ./a | ./b | ./c ./a | ./b | ./c当任何三个脚本的失败将失败。 返回代码将是第一个失败的脚本的返回码。

需要注意的是pipefail不标准的SH可用。



Answer 3:

您还可以检查${PIPESTATUS[]}全部执行后阵列,例如,如果你运行:

./a | ./b | ./c

然后${PIPESTATUS}将错误代码从在管的每个命令的数组,因此如果中间命令失败了, echo ${PIPESTATUS[@]}将包含这样的:

0 1 0

和类似的命令后,此运行:

test ${PIPESTATUS[0]} -eq 0 -a ${PIPESTATUS[1]} -eq 0 -a ${PIPESTATUS[2]} -eq 0

将让你检查,在管道中的所有命令成功。



Answer 4:

不幸的是,乔纳森的回答需要临时文件和米歇尔和英隆的答案,需要的bash(虽然这个问题被标记壳)。 正如其他人已经指出,这是不可能中止管后进程启动之前。 所有过程都开始于一次,因此将所有的运行可以传达任何错误了。 但问题的标题也被问有关错误代码。 这些可以被检索和调查后的管成品找出任何涉及的流程是否失败。

下面是捕捉在管道和所有的错误不是最后一个组件的唯一错误的解决方案。 所以这就像bash的pipefail,只是,你可以检索所有的错误代码的意义更加强大。

res=$( (./a 2>&1 || echo "1st failed with $?" >&2) |
(./b 2>&1 || echo "2nd failed with $?" >&2) |
(./c 2>&1 || echo "3rd failed with $?" >&2) > /dev/null 2>&1)
if [ -n "$res" ]; then
    echo pipe failed
fi

为了检测是否任何失败的echo在标准错误命令打印的情况下,任何命令失败。 然后合并标准错误输出保存在$res ,后来调查。 这也是为什么所有进程的标准错误重定向到标准输出。 您还可以发送输出到/dev/null或把它作为另一个指标,出事了。 您可以替换最后重定向到/dev/null同一个文件,如果哟Uneed公司存储的最后一个命令的输出的任何地方。

要使用此结构发挥越来越说服自己,这真的做什么应该,我换成./a./b./c由执行子shell echocatexit 。 您可以使用此功能来检查该构建所有的输出确实转发从一个进程到另一个的出错代码得到正确记录。

res=$( (sh -c "echo 1st out; exit 0" 2>&1 || echo "1st failed with $?" >&2) |
(sh -c "cat; echo 2nd out; exit 0" 2>&1 || echo "2nd failed with $?" >&2) |
(sh -c "echo start; cat; echo end; exit 0" 2>&1 || echo "3rd failed with $?" >&2) > /dev/null 2>&1)
if [ -n "$res" ]; then
    echo pipe failed
fi


文章来源: Catching error codes in a shell pipe