Catching error codes in a shell pipe

后端 未结 4 869
执笔经年
执笔经年 2020-11-27 13:14

I currently have a script that does something like

./a | ./b | ./c

I want to modify it so that if any of a, b or c exit with an error code

4条回答
  •  栀梦
    栀梦 (楼主)
    2020-11-27 13:17

    Unfortunately, the answer by Johnathan requires temporary files and the answers by Michel and Imron requires bash (even though this question is tagged shell). As pointed out by others already, it is not possible to abort the pipe before later processes are started. All processes are started at once and will thus all run before any errors can be communicated. But the title of the question was also asking about error codes. These can be retrieved and investigated after the pipe finished to figure out whether any of the involved processes failed.

    Here is a solution that catches all errors in the pipe and not only errors of the last component. So this is like bash's pipefail, just more powerful in the sense that you can retrieve all the error codes.

    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
    

    To detect whether anything failed, an echo command prints on standard error in case any command fails. Then the combined standard error output is saved in $res and investigated later. This is also why standard error of all processes is redirected to standard output. You can also send that output to /dev/null or leave it as yet another indicator that something went wrong. You can replace the last redirect to /dev/null with a file if yo uneed to store the output of the last command anywhere.

    To play more with this construct and to convince yourself that this really does what it should, I replaced ./a, ./b and ./c by subshells which execute echo, cat and exit. You can use this to check that this construct really forwards all the output from one process to another and that the error codes get recorded correctly.

    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
    

提交回复
热议问题