hug*_*omg 91 error-handling shell pipe
我目前有一个类似的脚本
./a | ./b | ./c
我想修改它,以便如果a,b或c中的任何一个退出并带有错误代码我打印错误消息并停止而不是向前输出错误的输出.
最简单/最干净的方法是什么?
Mic*_*mia 144
在bash中,您可以在文件的开头使用set -e和set -o pipefail../a | ./b | ./c当三个脚本中的任何一个失败时,后续命令将失败.返回码将是第一个失败脚本的返回码.
请注意,pipefail标准sh中不可用.
Imr*_*ron 40
您也可以${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
将允许您检查管道中的所有命令是否成功.
Jon*_*ler 19
如果您确实不希望第二个命令继续进行,直到第一个命令成功,那么您可能需要使用临时文件.简单版本是:
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 shell错误地处理了错误重定向而没有前面的'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当任何信号发生1 SIGHUP,2 SIGINT,3 SIGQUIT,13 SIGPIPE或15 SIGTERM时,第一个陷阱线表示"运行命令" ,或者0(当shell因任何原因退出时).如果您正在编写shell脚本,则最终陷阱只需要删除0上的陷阱,这是shell退出陷阱(您可以保留其他信号,因为该过程即将终止).
在原始流水线中,'c'在'a'完成之前从'b'读取数据是可行的 - 这通常是可取的(例如,它允许多个核工作).如果'b'是'排序'阶段,那么这将不适用 - 'b'必须先查看其所有输入,然后才能生成任何输出.
如果要检测哪个(哪些)命令失败,可以使用:
(./a || echo "./a exited with $?" 1>&2) |
(./b || echo "./b exited with $?" 1>&2) |
(./c || echo "./c exited with $?" 1>&2)
这很简单且对称 - 扩展到4部分或N部分管道是微不足道的.
使用'set -e'进行简单的实验并没有帮助.
不幸的是,Johnathan的答案需要临时文件,而Michel和Imron的答案需要bash(即使这个问题是标记的shell).正如其他人已经指出的那样,在以后的进程开始之前不可能中止管道.所有进程都会立即启动,因此在所有错误都可以传达之前都会运行.但问题的标题也是询问错误代码.管道完成后可以检索和调查这些,以确定是否有任何相关的流程失败.
这是一个捕获管道中所有错误的解决方案,而不仅仅是最后一个组件的错误.所以这就像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如果你没有将最后一个命令的输出存储在任何地方,你可以用文件替换最后一个重定向.
为了更多地使用这个结构,并说服自己这确实做了它应该做的事情,我替换了./a,./b并且./c通过子shell执行echo,cat和exit.您可以使用它来检查此构造是否真正将所有输出从一个进程转发到另一个进程,并且错误代码被正确记录.
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