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


421

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

그래서 나는 이것을한다 :

command | tee out.txt
ST=$?

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

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


1
[[ "$ {PIPESTATUS [@]}"= ~ [^ 0 \]]] && -e "일치-오류가 발견되었습니다"|| echo -e "일치하지 않음-모두 양호"배열의 모든 값을 한 번에 테스트하고 반환 된 파이프 값이 0이 아닌 경우 오류 메시지를 표시합니다. 파이프 된 상황에서 오류를 감지하기위한 매우 강력한 일반화 된 솔루션입니다.
Brian S. Wilson

답변:


519

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

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

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

set -o pipefail
...

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


21
PIPESTATUS 및 Pipefail의 예에 대한 설명은 unix.stackexchange.com/a/73180/7453에 있습니다.
slm

18
참고 : $ PIPESTATUS [0]은 파이프에서 첫 번째 명령의 종료 상태, $ PIPESTATUS [1] 두 번째 명령의 종료 상태 등을 보유합니다.
simpleuser

18
물론 우리는 이것이 Bash 전용이라는 것을 기억해야합니다. 예를 들어, 안드로이드 기기에서 BusyBox의 "sh"구현 또는 다른 "sh"를 사용하는 다른 임베디드 플랫폼에서 실행할 스크립트를 작성하는 경우 변형이 작동하지 않습니다.
Asfand Qazi

4
인용되지 않은 변수 확장에 관심이있는 사람들 : 종료 상태는 항상 Bash에서 부호없는 8 비트 정수 이므로 인용 할 필요가 없습니다. 이것은 일반적으로 종료 상태가 8 비트로 정의 되는 Unix 에서도 유지되며 POSIX 자체에서도 서명되지 않은 것으로 간주됩니다 (예 : 논리적 부정 정의) .
Palec

3
을 사용할 수도 있습니다 exit ${PIPESTATUS[0]}.
Chaoran

142

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

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


23
전체 스크립트의 pipefail 설정을 수정하지 않으려는 경우 옵션을 로컬로만 설정할 수 있습니다.( set -o pipefail; command | tee out.txt ); ST=$?
Jaan

7
@Jaan 서브 쉘을 실행합니다. 이를 피 set -o pipefail하려면 명령을 수행 한 다음 즉시 수행 set +o pipefail하고 옵션을 설정 해제하십시오.
Linus Arver

2
참고 : 질문 포스터는 파이프의 "일반 종료 코드"를 원하지 않고 'command'의 리턴 코드를 원합니다. 와 -o pipefail파이프가 실패 할 경우 자신이 알고있는 것이지만, '명령'과 '티'모두가 실패 할 경우, 그는 '티'에서 종료 코드를받을 것입니다.
t0r0X 9

@LinusArver는 성공한 명령이기 때문에 종료 코드를 지우지 않습니까?
carlin.scott

127

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

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

20
이것은 간단한 sh Unix 쉘 에서도 작동하는이 질문의 유일한 대답입니다 . 감사!
JamesThomasMoon1979

3
@DaveKennedy : "분명하고, bash 구문에 대한 복잡한 지식이 필요하지 않습니다"
EFraim

10
bash의 추가 기능을 사용할 때 bash 응답이 더 우아하지만 이는 교차 플랫폼 솔루션입니다. 또한 장기 실행 명령을 할 때마다 이름 파이프가 가장 유연한 방법이므로 일반적으로 생각해 볼 가치가 있습니다. 일부 시스템 mkfifo에는 mknod -p내가 기억 하지 않으면 필요하지 않고 대신 필요할 수 있습니다 .
Haravikk

3
때로는 스택 오버플로에서 사람들이 말도 안되는 다른 일을 중단 할 수 있도록 백 번의 투표를하는 답변이 있습니다. 이것은 그중 하나입니다. 감사합니다.
Dan Chase

1
누군가 mkfifo또는에 문제가있는 경우 mknod -p: 내 경우에는 파이프 파일을 만드는 적절한 명령이었습니다 mknod FILE_NAME p.
Karol Gil

36

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

$ 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

26

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

상태:

someprog | filter

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

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

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

echo $?

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


20

서브 쉘 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


4
덕분에 VALUE=$(might_fail | piping)마스터 쉘에서 PIPESTATUS를 설정하지 않지만 오류 수준을 설정 하는 구문을 사용할 수있었습니다 . 사용하여 : VALUE=$(might_fail | piping; exit ${PIPESTATUS[0]})나는 내가 원하는 것을 얻습니다.
vaab

@ vaab, 그 구문은 정말 멋지지만 문맥에서 '파이핑'이 무엇을 의미하는지 혼란 스럽습니까? 그것이 might_fail의 출력에 대해 'tee'또는 다른 처리를 수행하는 곳입니까? 타이!
AnneTheAgile

1
내 예제에서 @AnneTheAgile 'piping'은 errlvl을보고 싶지 않은 명령을 나타냅니다. 예를 들어, 'tee', 'grep', 'sed'등의 파이프 조합 중 하나 또는 ... 이러한 파이프 명령이 메인의 더 큰 출력 또는 로그 출력에서 ​​정보를 형식화하거나 추출하는 것은 드문 일이 아닙니다. command : 그런 다음 주 명령의 errlevel에 더 관심이 있지만 (내 예제에서 'might_fail'이라고 한 명령) 내 구성이 없으면 전체 할당이 마지막으로 파이프 된 명령의 errlvl을 반환합니다. 이것이 더 명확합니까?
vaab

command_might_fail | grep -v "line_pattern_to_exclude" || exit ${PIPESTATUS[0]}티가 아니라 grep 필터링의 경우
user1742529

12

그래서 나는 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에 직접 작성합니다. 추측 (파일 디스크립터가 열려 있는지 확인하고 열려있는 경우 사용하거나 그렇지 않으면 다르게 동작하는 프로그램을 상상할 수 있습니다). 따라서 후자는 아마도 명심하고 일반적인 경우에 사용하는 것이 가장 좋습니다.


좋은 설명입니다!
selurvedu

6

우분투와 데비안에서는 할 수 있습니다 apt-get install moreutils. 여기에는 mispipe파이프에서 첫 번째 명령의 종료 상태를 리턴 하는 유틸리티가 포함되어 있습니다 .


5
(command | tee out.txt; exit ${PIPESTATUS[0]})

@cODAR의 답변과 달리 이것은 첫 번째 명령의 원래 종료 코드를 반환하며 성공은 0, 실패는 127입니다. 그러나 @Chaoran이 지적한 것처럼 전화하면 ${PIPESTATUS[0]}됩니다. 그러나 모두 괄호 안에 넣는 것이 중요합니다.


4

bash 외부에서 다음을 수행 할 수 있습니다.

bash -o pipefail  -c "command1 | tee output"

예를 들어 쉘이 예상되는 닌자 스크립트에서 유용합니다 /bin/sh.


3

파이프 명령이 반환 된 직후 PIPESTATUS [@]를 배열에 복사해야합니다. 어느 PIPESTATUS의 읽기 [@] 내용을 지 웁니다. 모든 파이프 명령의 상태를 확인하려면 다른 배열로 복사하십시오. "$?" "$ {PIPESTATUS [@]}"의 마지막 요소와 동일한 값이며, "$ {PIPESTATUS [@]}"을 (를) 파기하는 것으로 보이지만이를 완전히 확인하지는 않았습니다.

declare -a PSA  
cmd1 | cmd2 | cmd3  
PSA=( "${PIPESTATUS[@]}" )

파이프가 하위 셸에 있으면 작동하지 않습니다. 해당 문제점에 대한 솔루션 은 backticked 명령의 bash pipestatus를
참조하십시오 .


3

일반 bash에서이를 수행하는 가장 간단한 방법 은 파이프 라인 대신 프로세스 대체 를 사용 하는 것입니다. 몇 가지 차이점이 있지만 유스 케이스에는별로 중요하지 않습니다.

  • 파이프 라인을 실행할 때 bash는 모든 프로세스가 완료 될 때까지 기다립니다.
  • bash에 Ctrl-C를 보내면 주요 파이프 라인뿐만 아니라 파이프 라인의 모든 프로세스가 종료됩니다.
  • pipefail옵션과 PIPESTATUS변수는 프로세스 대체 무관하다.
  • 아마도 더

프로세스 대체를 사용하면 bash는 프로세스를 시작하고 잊어 버립니다 jobs.

차이점을 제외 consumer < <(producer)하고 producer | consumer본질적으로 동일합니다.

"메인"프로세스 중 하나를 뒤집으려면 명령과 대체 방향을로 바꾸십시오 producer > >(consumer). 귀하의 경우 :

command > >(tee out.txt)

예:

$ { echo "hello world"; false; } > >(tee out.txt)
hello world
$ echo $?
1
$ cat out.txt
hello world

$ echo "hello world" > >(tee out.txt)
hello world
$ echo $?
0
$ cat out.txt
hello world

내가 말했듯이 파이프 표현과는 차이가 있습니다. 파이프 닫힘에 민감한 프로세스가 아니라면 프로세스가 중단되지 않을 수 있습니다. 특히, 그것은 stdout에 물건을 계속 쓸 수 있으며, 이는 혼란 스러울 수 있습니다.


1

순수한 쉘 솔루션 :

% rm -f error.flag; echo hello world \
| (cat || echo "First command failed: $?" >> error.flag) \
| (cat || echo "Second command failed: $?" >> error.flag) \
| (cat || echo "Third command failed: $?" >> error.flag) \
; test -s error.flag  && (echo Some command failed: ; cat error.flag)
hello world

그리고 이제 두 번째 cat로 대체되었습니다 false.

% rm -f error.flag; echo hello world \
| (cat || echo "First command failed: $?" >> error.flag) \
| (false || echo "Second command failed: $?" >> error.flag) \
| (cat || echo "Third command failed: $?" >> error.flag) \
; test -s error.flag  && (echo Some command failed: ; cat error.flag)
Some command failed:
Second command failed: 1
First command failed: 141

첫 번째 고양이는 stdout이 닫히기 때문에 실패합니다. 이 예제에서는 로그에서 실패한 명령의 순서는 정확하지만 의존하지는 않습니다.

이 방법을 사용하면 개별 명령에 대해 stdout 및 stderr을 캡처 할 수 있으므로 오류가 발생한 경우 로그 파일로 덤프하거나 오류가없는 경우 (dd의 출력과 같은 경우) 로그 파일을 삭제할 수 있습니다.


1

@ brian-s-wilson의 답변을 바탕으로합니다. 이 bash 도우미 기능 :

pipestatus() {
  local S=("${PIPESTATUS[@]}")

  if test -n "$*"
  then test "$*" = "${S[*]}"
  else ! [[ "${S[@]}" =~ [^0\ ] ]]
  fi
}

따라서 사용 :

1 : get_bad_things는 성공해야하지만 출력을 생성하지 않아야합니다. 그러나 우리는 그것이 생산하는 결과물을보고 싶습니다

get_bad_things | grep '^'
pipeinfo 0 1 || return

2 : 모든 파이프 라인이 성공해야합니다

thing | something -q | thingy
pipeinfo || return

1

bash의 세부 사항을 파고 들지 않고 외부 명령을 사용하는 것이 때로는 더 간단하고 명확 할 수 있습니다. 파이프 라인은 , 최소한의 프로세스 스크립트 언어에서 execline 반환 정당한처럼 두 번째 명령 *의 코드와 종료 sh파이프 라인 않지만, 달리가 sh, 우리가 생산자의 반환 코드를 캡처 할 수 있도록, 파이프의 방향을 반대로 할 수 있습니다 프로세스 (아래는 모두 sh명령 행에 있지만 execline설치되어 있음) :

$ # using the full execline grammar with the execlineb parser:
$ execlineb -c 'pipeline { echo "hello world" } tee out.txt'
hello world
$ cat out.txt
hello world

$ # for these simple examples, one can forego the parser and just use "" as a separator
$ # traditional order
$ pipeline echo "hello world" "" tee out.txt 
hello world

$ # "write" order (second command writes rather than reads)
$ pipeline -w tee out.txt "" echo "hello world"
hello world

$ # pipeline execs into the second command, so that's the RC we get
$ pipeline -w tee out.txt "" false; echo $?
1

$ pipeline -w tee out.txt "" true; echo $?
0

$ # output and exit status
$ pipeline -w tee out.txt "" sh -c "echo 'hello world'; exit 42"; echo "RC: $?"
hello world
RC: 42
$ cat out.txt
hello world

을 사용하면 pipeline응답 # 43972501에 사용 된 bash 프로세스 대체와 기본 bash 파이프 라인이 다릅니다 .

* 실제로 pipeline오류가 없으면 종료하지 않습니다. 두 번째 명령으로 실행되므로 반환하는 두 번째 명령입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.