[bash] Bash에서 파이프 출력 및 캡처 종료 상태

Bash에서 장시간 실행되는 명령을 실행하고 종료 상태를 캡처 하고 출력을 티피 하고 싶습니다 .

그래서 나는 이것을한다 :

command | tee out.txt
ST=$?

문제는 변수 ST tee가 명령이 아닌 종료 상태를 캡처한다는 것 입니다. 이 문제를 어떻게 해결할 수 있습니까?

명령을 오랫동안 실행하고 나중에 볼 수 있도록 출력을 파일로 리디렉션하는 것은 좋은 해결책이 아닙니다.



답변

내부 Bash 변수가 있습니다 $PIPESTATUS; 마지막 포 그라운드 파이프 라인 파이프 라인에서 각 명령의 종료 상태를 유지하는 배열입니다.

<command> | tee out.txt ; test ${PIPESTATUS[0]} -eq 0

또는 다른 쉘 (zsh와 같은)에서도 작동하는 다른 대안은 pipefail을 활성화하는 것입니다.

set -o pipefail
...

첫 번째 옵션은 약간 다른 구문으로 인해 작동 하지 않습니다zsh .


답변

bash를 사용하면 set -o pipefail도움이됩니다.

pipefail : 파이프 라인의 반환 값은 0이 아닌 상태로 종료하는 마지막 명령의 상태이거나 0이 아닌 상태로 종료 된 명령이없는 경우 0입니다.


답변

벙어리 솔루션 : 명명 된 파이프 (mkfifo)를 통해 연결. 그런 다음 명령을 두 번째로 실행할 수 있습니다.

 mkfifo pipe
 tee out.txt < pipe &
 command > pipe
 echo $?


답변

파이프에있는 각 명령의 종료 상태를 제공하는 배열이 있습니다.

$ cat x| sed 's///'
cat: x: No such file or directory
$ echo $?
0
$ cat x| sed 's///'
cat: x: No such file or directory
$ echo ${PIPESTATUS[*]}
1 0
$ touch x
$ cat x| sed 's'
sed: 1: "s": substitute pattern can not be delimited by newline or backslash
$ echo ${PIPESTATUS[*]}
0 1


답변

이 솔루션은 bash 특정 기능이나 임시 파일을 사용하지 않고 작동합니다. 보너스 : 결국 종료 상태는 실제로 종료 상태이며 파일의 일부 문자열이 아닙니다.

상태:

someprog | filter

종료 상태 someprog와 출력 을 원합니다 filter.

내 해결책은 다음과 같습니다.

((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1

echo $?

자세한 설명과 서브 쉘이없는 대안 및주의 사항 은 unix.stackexchange.com에서 동일한 질문에 대한 내 대답을 참조하십시오 .


답변

서브 쉘 PIPESTATUS[0]에서 exit명령 을 실행 한 결과와 결합 하여 초기 명령의 리턴 값에 직접 액세스 할 수 있습니다.

command | tee ; ( exit ${PIPESTATUS[0]} )

예를 들면 다음과 같습니다.

# the "false" shell built-in command returns 1
false | tee ; ( exit ${PIPESTATUS[0]} )
echo "return value: $?"

당신에게 줄 것이다 :

return value: 1


답변

그래서 나는 lesmana와 같은 답변을 제공하고 싶었지만 내 것이 아마도 더 간단하고 약간 더 유익한 순수한 Bourne-shell 솔루션이라고 생각합니다.

# You want to pipe command1 through command2:
exec 4>&1
exitstatus=`{ { command1; printf $? 1>&3; } | command2 1>&4; } 3>&1`
# $exitstatus now has command1's exit status.

나는 이것이 내부에서 가장 잘 설명된다고 생각합니다-command1은 stdout (파일 설명자 1)에서 일반 출력을 실행하고 인쇄 한 다음 일단 완료되면 printf가 stdout에서 icommand1의 종료 코드를 실행하고 인쇄하지만 stdout은 파일 기술자 3.

command1이 실행되는 동안 stdout은 command2로 파이프됩니다 (printf의 출력은 파이프가 읽는 1이 아니라 파일 디스크립터 3으로 보내므로 command2로 보내지 않습니다). 그런 다음 command2의 출력을 파일 디스크립터 4로 재지 정하여 파일 디스크립터 1을 유지합니다. 파일 디스크립터 1의 출력을 파일 디스크립터 3으로 다시 가져 오기 때문에 파일 디스크립터 1을 약간 나중에 비워야합니다. 1-이것이 명령 대체 (백틱)이며 캡처하고 변수에 배치되기 때문입니다.

마술의 마지막 부분은 먼저 exec 4>&1별도의 명령으로 수행 한 것입니다. 파일 설명자 4를 외부 쉘의 stdout의 사본으로 엽니 다. 명령 대체는 내부 명령의 관점에서 표준에 쓰여진 모든 것을 캡처하지만 명령 대체와 관련하여 command2의 출력은 파일 설명자 4로 이동하므로 명령 대체는 캡처하지 않습니다. “대체”된 명령 대체는 여전히 스크립트의 전체 파일 디스크립터 1로 진행됩니다.

( exec 4>&1대체 쉘은 명령 대체 내에서 파일 디스크립터에 쓰려고 할 때이를 대체하지 않는 별도의 명령이어야합니다.이 대체는 대체를 사용하는 “외부”명령에서 열립니다. 가장 간단한 휴대용 방법입니다.)

명령의 출력이 서로 뛰어 넘는 것처럼 덜 기술적이고 더 유쾌한 방식으로 볼 수 있습니다. printf가 제 시간에 도달하여 변수로 끝나고 command2의 출력이 표준 출력에 기록되는 것과 같은 방식으로 진행되는 것처럼 command 2의 출력은 명령 대체로 건너 뛰고 빠져 나옵니다. 일반 파이프에서.

또한 내가 이해하는 것처럼 $?변수 할당, 명령 대체 및 복합 명령은 모두 내부 명령의 리턴 코드에 효과적으로 투명하므로 파이프에 두 번째 명령의 리턴 코드가 여전히 포함됩니다. command2가 전파되어야합니다-이것은 추가 기능을 정의 할 필요가 없기 때문에 이것이 lesmana가 제안한 것보다 다소 더 나은 해결책이라고 생각합니다.

lesmana가 언급 한 경고에 따르면, command1은 파일 디스크립터 3 또는 4를 사용하여 어느 시점에서 종료 될 수 있으므로보다 강력 해집니다.

exec 4>&1
exitstatus=`{ { command1 3>&-; printf $? 1>&3; } 4>&- | command2 1>&4; } 3>&1`
exec 4>&-

필자의 예제에서는 복합 명령을 사용하지만 하위 쉘 ( 대체로 사용 ( )하는 { }것도 효과적이지만 효율성이 떨어질 수 있음)을 참고하십시오.

명령은 파일 디스크립터를 시작하는 프로세스에서 파일 디스크립터를 상속하므로 전체 두 번째 행은 파일 디스크립터 4를 상속하고 복합 명령 다음에 3>&1파일 디스크립터 3을 상속합니다. 따라서 4>&-내부 복합 명령이 파일 디스크립터 4를 3>&-상속하지 않고 파일 디스크립터 3을 상속하지 않으므로 command1은 더 깨끗하고 표준적인 환경을 얻습니다. 4>&-옆으로 내부를 이동할 수도 3>&-있지만 가능한 한 범위를 제한하지 않는 이유는 무엇입니까?

일이 얼마나 자주 파일 디스크립터 3과 4를 직접 사용하는지 잘 모르겠습니다. 프로그램에서 대부분의 시간에 사용하지 않는 파일 디스크립터를 리턴하는 syscall을 사용한다고 생각하지만 때로는 코드가 파일 디스크립터 3에 직접 작성합니다. 추측 (파일 디스크립터가 열려 있는지 확인하고 열려있는 경우 사용하거나 그렇지 않으면 다르게 동작하는 프로그램을 상상할 수 있습니다). 따라서 후자는 아마도 명심하고 일반적인 경우에 사용하는 것이 가장 좋습니다.