programing tip

쉘 파이프에서 오류 코드 포착

itbloger 2020. 9. 5. 09:24
반응형

쉘 파이프에서 오류 코드 포착


현재 다음과 같은 스크립트가 있습니다.

./a | ./b | ./c

a, b 또는 c 중 하나가 오류 코드와 함께 종료되면 오류 메시지를 인쇄하고 잘못된 출력을 앞으로 파이프하는 대신 중지하도록 수정하고 싶습니다.

그렇게하는 가장 간단하고 깨끗한 방법은 무엇입니까?


첫 번째 명령이 성공할 때까지 두 번째 명령을 진행하지 않으려면 임시 파일을 사용해야합니다. 그 간단한 버전은 다음과 같습니다.

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'없이 오류 리디렉션을 잘못 처리했기 때문에 오랫동안 안정성을 위해 모호하지 않은 표기법을 사용했습니다.

방해하면 파일이 유출됩니다. 방폭형 (약간) 쉘 프로그래밍은 다음을 사용합니다.

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 (어떤 이유로 든 쉘이 종료되는 경우)입니다. 쉘 스크립트를 작성하는 경우 최종 트랩은 쉘 종료 트랩 인 0의 트랩 만 제거하면됩니다 (프로세스가 종료 되려고하므로 다른 신호는 그대로 둘 수 있습니다).

원래 파이프 라인에서는 'a'가 완료되기 전에 'c'가 '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'를 사용한 간단한 실험은 도움이되지 않았습니다.


bash 에서는 파일 시작 부분에 set -eset -o pipefail사용할 수 있습니다 . ./a | ./b | ./c세 스크립트 중 하나라도 실패하면 후속 명령 이 실패합니다. 반환 코드는 첫 번째 실패한 스크립트의 반환 코드입니다.

참고 pipefail표준에서 사용할 수 없습니다 .


${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

will allow you to check that all commands in the pipe succeeded.


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

참고URL : https://stackoverflow.com/questions/1550933/catching-error-codes-in-a-shell-pipe

반응형