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


97

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

./a | ./b | ./c

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

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


7
&&|"이전 명령이 성공한 경우에만 파이프를 계속하십시오"를 의미하는 것과 같은 것이 필요합니다 . |||"이전 명령이 실패하면 파이프를 계속하십시오"(그리고 Bash 4와 같은 오류 메시지를 파이프 할 수 있음) 를 의미 할 수도 있습니다 |&.
추후 공지가있을 때까지 일시 중지되었습니다.

6
때문에 @DennisWilliamson, 당신은 "파이프를 중지 할 수 없습니다" a, b, c명령은 순차적으로 만 병렬로 실행되지 않습니다. 즉, 데이터가 순차적으로 흐름 ac있지만 실제 a, bc명령은 동시에 (대략적으로) 시작한다.
Giacomo

답변:


20

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

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'를 사용한 간단한 실험은 도움이되지 않았습니다.


1
mktemp또는을 사용하는 것이 좋습니다 tempfile.
추후 공지가있을 때까지 일시 중지되었습니다.

@Dennis : 예, mktemp 또는 tmpfile과 같은 명령에 익숙해 져야한다고 생각합니다. 내가 그것을 배웠을 때 그들은 셸 수준에서 존재하지 않았습니다. 간단하게 확인해 보겠습니다. MacOS X에서 mktemp를 찾습니다. Solaris에 mktemp가 있지만 GNU 도구를 설치했기 때문입니다. mktemp가 골동품 HP-UX에있는 것 같습니다. 플랫폼간에 작동하는 mktemp의 일반적인 호출이 있는지 확실하지 않습니다. POSIX는 mktemp도 tmpfile도 표준화하지 않습니다. 액세스 권한이있는 플랫폼에서 tmpfile을 찾지 못했습니다. 따라서 이식 가능한 쉘 스크립트에서 명령을 사용할 수 없습니다.
Jonathan Leffler

1
사용시 trap사용자는 항상 SIGKILL프로세스에 전송 하여 즉시 종료 할 수 있으며이 경우 트랩이 적용되지 않습니다. 시스템에 정전이 발생한 경우에도 마찬가지입니다. 임시 파일을 만들 때 mktemp파일을 어딘가에 저장하고 재부팅 후 정리되는 (일반적으로 /tmp) 파일 을 사용하려면을 사용하십시오 .
josch

155

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

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


나는 pipefail에 대해 몰랐다. 정말 편리했다.
Phil Jackson

대화 형 셸이 아닌 스크립트에 삽입하기위한 것입니다. -e를 설정하기 위해 동작을 단순화 할 수 있습니다. 그릇된; 또한 예상되는 동작 인 쉘 프로세스를 종료합니다.)
Michel Samia

11
참고 : 이것은 여전히 ​​세 스크립트를 모두 실행하고 첫 번째 오류에서 파이프를 중지하지 않습니다.
hyde

1
@ n2liquid-GuilhermeVieira Btw, "다른 변형"이라는 말은 구체적으로 하나 또는 둘 모두를 제거하고 set(총 4 개의 다른 버전에 대해) 마지막 echo.
hyde

1
@josch Google의 bash에서이 작업을 수행하는 방법을 검색 할 때이 페이지를 찾았지만 "이것이 정확히 내가 찾고있는 것입니다". 나는 많은 사람들이 비슷한 생각 과정을 거치고 태그와 태그의 정의를 확인하지 않는 것으로 생각합니다.
Troy Daniels

43

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

파이프의 모든 명령이 성공했는지 확인할 수 있습니다.


11
이것은 bashish --- bash 확장이며 Posix 표준의 일부가 아니므로 dash 및 ash와 같은 다른 쉘은이를 지원하지 않습니다. 시작 스크립트에서 사용하려고하면이 방법은 당신이 문제로 실행할 수있는 #!/bin/sh경우가 있기 때문에, shbash는하지가 작동하지 않습니다. (사용을 기억하여 쉽게 고칠 #!/bin/bash대신은.)
데이비드을 감안할 때

2
echo ${PIPESTATUS[@]} | grep -qE '^[0 ]+$'$PIPESTATUS[@]-0과 공백 만 포함 하면 0을 반환 합니다 (파이프의 모든 명령이 성공한 경우).
MattBianco

@MattBianco 이것은 나를위한 최고의 솔루션입니다. 또한 &&와 함께 작동합니다. 예를 들어 command1 && command2 | command3이들 중 하나라도 실패하면 솔루션이 0이 아닌 값을 반환합니다.
DavidC

8

불행히도 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실행 서브 쉘에 의해 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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.