현재 다음과 같은 스크립트가 있습니다.
./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 -e
및 set -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
파이프의 모든 명령이 성공했는지 확인할 수 있습니다.
답변
불행히도 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