다른 프로세스로 파이프 된 프로세스의 종료 상태 가져 오기


답변:


256

bash사용하는 경우 PIPESTATUS배열 변수를 사용 하여 파이프 라인의 각 요소의 종료 상태를 얻을 수 있습니다 .

$ false | true
$ echo "${PIPESTATUS[0]} ${PIPESTATUS[1]}"
1 0

를 사용하는 경우 zsh배열이 호출되고 pipestatus(사건이 중요합니다) 배열 색인이 하나에서 시작됩니다.

$ false | true
$ echo "${pipestatus[1]} ${pipestatus[2]}"
1 0

값을 잃지 않는 방식으로 함수 내에서 그것들을 결합하려면 :

$ false | true
$ retval_bash="${PIPESTATUS[0]}" retval_zsh="${pipestatus[1]}" retval_final=$?
$ echo $retval_bash $retval_zsh $retval_final
1 0

위의 실행 bash또는 zsh당신은 동일한 결과를 얻을 수 있습니다; 단 하나 retval_bash와이 retval_zsh설정됩니다. 다른 하나는 비어 있습니다. 이렇게하면 함수가 끝날 수 있습니다 return $retval_bash $retval_zsh(따옴표가 없습니다!).


9
그리고 pipestatuszsh에서. 불행히도 다른 쉘에는이 기능이 없습니다.
Gilles

8
참고 : zsh의 배열은 색인 1에서 반 직관적으로 시작하므로 echo "$pipestatus[1]" "$pipestatus[2]".
Christoph Wurm

5
전체 파이프 라인을 다음과 같이 확인할 수 있습니다.if [ `echo "${PIPESTATUS[@]}" | tr -s ' ' + | bc` -ne 0 ]; then echo FAIL; fi
l0b0

7
@ JanHudec : 아마도 내 대답의 처음 다섯 단어를 읽어야 할 것입니다. 또한 질문이 POSIX 전용 답변을 요청한 곳을 친절하게 지적하십시오.
camh

12
@ JanHudec : POSIX로 태그되지 않았습니다. 왜 대답이 POSIX 여야한다고 가정합니까? 규정되지 않았으므로 자격있는 답변을 제공했습니다. 내 답변에 대해 잘못된 것은 없으며 다른 경우를 해결하기 위해 여러 답변이 있습니다.
camh

237

이 작업을 수행하는 일반적인 3 가지 방법이 있습니다.

파이프 페일

첫 번째 방법은 pipefail옵션 ( ksh, zsh또는 bash) 을 설정하는 것 입니다. 이것은 가장 간단하며 기본적으로 종료 상태 $?를 마지막 프로그램의 종료 코드로 설정하여 0이 아닌 종료합니다 (또는 모두 성공적으로 종료 된 경우 0).

$ false | true; echo $?
0
$ set -o pipefail
$ false | true; echo $?
1

$ PIPESTATUS

Bash 에는 마지막 파이프 라인에있는 모든 프로그램의 종료 상태를 포함하는 $PIPESTATUS( $pipestatusin zsh) 라는 배열 변수도 있습니다 .

$ true | true; echo "${PIPESTATUS[@]}"
0 0
$ false | true; echo "${PIPESTATUS[@]}"
1 0
$ false | true; echo "${PIPESTATUS[0]}"
1
$ true | false; echo "${PIPESTATUS[@]}"
0 1

세 번째 명령 예제를 사용하여 필요한 파이프 라인의 특정 값을 얻을 수 있습니다.

별도의 처형

이것은 가장 다루기 힘든 솔루션입니다. 각 명령을 개별적으로 실행하고 상태를 캡처하십시오.

$ OUTPUT="$(echo foo)"
$ STATUS_ECHO="$?"
$ printf '%s' "$OUTPUT" | grep -iq "bar"
$ STATUS_GREP="$?"
$ echo "$STATUS_ECHO $STATUS_GREP"
0 1

2
꿰매다! PIPESTATUS에 대해 게시하려고했습니다.
slm

1
참고로이 SO 질문에서 논의 된 몇 가지 다른 기술이 있습니다 : stackoverflow.com/questions/1221833/…
slm

1
@Patrick pipestatus 솔루션은 bash에서 작동합니다. ksh 스크립트를 사용하는 경우 더 많은 quastion을 사용하면 pipestatus와 비슷한 것을 찾을 수 있다고 생각합니까? , (ksh가 지원하지 않는 pipestatus를 보는 동안)
yael

2
@yael을 사용하지 않지만 ksh맨 페이지를 간략히 살펴보면 지원하지 않거나 $PIPESTATUS비슷한 것이 없습니다 . pipefail그래도 옵션을 지원합니다 .
Patrick

1
여기서 실패한 명령의 상태를 얻을 수 있도록 pipefail을 사용하기로 결정했습니다.LOG=$(failed_command | successful_command)
vmrob

51

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

상태:

someprog | filter

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

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

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

이 구성의 결과는 구성의 stdout에서 filterstdout이고 구성의 종료 상태에서 someprog종료 상태입니다.


이 구문 {...}은 서브 쉘 대신 간단한 명령 그룹화와도 작동합니다 (...). 서브 쉘은 무엇보다도 성능 비용에 영향을 미치며 여기서는 필요하지 않습니다. 자세한 내용은 훌륭한 bash 매뉴얼을 읽으십시오 : https://www.gnu.org/software/bash/manual/html_node/Command-Grouping.html

{ { { { someprog; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; } } 4>&1

불행히도 bash 문법에는 중괄호에 공백과 세미콜론이 필요하므로 구문이 훨씬 넓어집니다.

이 텍스트의 나머지 부분에서는 서브 쉘 변형을 사용합니다.


someprogfilter:

someprog() {
  echo "line1"
  echo "line2"
  echo "line3"
  return 42
}

filter() {
  while read line; do
    echo "filtered $line"
  done
}

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

echo $?

출력 예 :

filtered line1
filtered line2
filtered line3
42

참고 : 자식 프로세스는 부모로부터 열린 파일 설명자를 상속합니다. 이는 someprog열린 파일 설명자 3과 4를 상속 함을 의미 합니다. 파일 설명자 3에 someprog쓰면 종료 상태가됩니다. read한 번만 읽기 때문에 실제 종료 상태는 무시 됩니다.

someprog파일 디스크립터 3 또는 4에 쓸 수 있다고 걱정되면을 호출하기 전에 파일 디스크립터를 닫는 것이 가장 좋습니다 someprog.

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

exec 3>&- 4>&-전에 someprog실행하기 전에 파일 기술자를 닫 someprog위해 이렇게 someprog설명 단순히 존재하지 않는 파일입니다.

다음과 같이 쓸 수도 있습니다 : someprog 3>&- 4>&-


구성에 대한 단계별 설명 :

( ( ( ( someprog;          #part6
        echo $? >&3        #part5
      ) | filter >&4       #part4
    ) 3>&1                 #part3
  ) | (read xs; exit $xs)  #part2
) 4>&1                     #part1

아래에서 위로 :

  1. 서브 쉘은 파일 디스크립터 4가 stdout으로 경로 재 지정되어 작성됩니다. 이것은 서브 쉘에서 파일 디스크립터 4에 인쇄 된 모든 것이 전체 구성의 표준 출력으로 끝나는 것을 의미합니다.
  2. 파이프가 생성되고 왼쪽 ( #part3) 및 오른쪽 ( #part2) 의 명령 이 실행됩니다. exit $xs또한 파이프의 마지막 명령이며 stdin의 문자열이 전체 구문의 종료 상태가됨을 의미합니다.
  3. 파일 디스크립터 3이 stdout으로 경로 재 지정된 서브 쉘이 작성됩니다. 이것은이 서브 쉘에서 파일 디스크립터 3에 인쇄 된 것은 결국 #part2전체 구성의 종료 상태 가됨을 의미합니다 .
  4. 파이프가 생성되고 왼쪽 ( #part5#part6) 및 오른쪽 ( filter >&4) 의 명령 이 실행됩니다. 의 출력 filter에서 설명 4. 파일로 재되는 #part1파일 디스크립터 4 표준 출력으로 리디렉션한다. 이것은 출력이 filter전체 구성의 표준 출력임을 의미합니다 .
  5. 종료 상태 #part6#part3파일 설명자 3으로 인쇄됩니다. 파일 설명자 3이 (가)로 리디렉션되었습니다 #part2. 이는 #part6종료 상태가 전체 구성에 대한 최종 종료 상태가 됨을 의미합니다 .
  6. someprog실행됩니다. 종료 상태는입니다 #part5. stdout은 파이프로 가져와로 #part4전달됩니다 filter. 의 출력은 filter차례로 설명 된대로 표준 출력에 도달합니다.#part4

1
좋은. 내가 할 수있는 기능을 위해(read; exit $REPLY)
jthill

5
(exec 3>&- 4>&-; someprog)로 단순화합니다 someprog 3>&- 4>&-.
Roger Pate

이 방법은 서브 쉘 없이도 작동합니다.{ { { { someprog 3>&- 4>&-; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; }; } 4>&1
josch

36

정확히 당신이 요구 한 것은 아니지만 사용할 수 있습니다.

#!/bin/bash -o pipefail

파이프가 0이 아닌 마지막 리턴을 리턴하도록합니다.

코딩이 약간 적을 수 있습니다

편집 : 예

[root@localhost ~]# false | true
[root@localhost ~]# echo $?
0
[root@localhost ~]# set -o pipefail
[root@localhost ~]# false | true
[root@localhost ~]# echo $?
1

9
set -o pipefail예를 들어 누군가가를 통해 스크립트를 실행하는 경우 스크립트 내부가 더 강력해야합니다 bash foo.sh.
maxschlepzig 2018 년

어떻게 작동합니까? 예가 있습니까?
Johan

2
그 주 -o pipefailPOSIX에 있지 않습니다.
scy

2
내 BASH 3.2.25 (1) 릴리스에서는 작동하지 않습니다. / tmp / ff의 맨 위에 있습니다 #!/bin/bash -o pipefail. 오류 :/bin/bash: line 0: /bin/bash: /tmp/ff: invalid option name
Felipe Alvarez

2
@FelipeAlvarez : 리눅스를 포함한 일부 환경 #!은 첫 번째 환경을 넘어선 행을 공백으로 파싱하지 않으므로 , 다음 항목 인을 사용하여 /bin/bash -o pipefail /tmp/ff필요한 /bin/bash -o pipefail /tmp/ff- getopt(또는 비슷한) 파싱 대신에 - optargARGV됩니다. 에 -o실패하므로 실패합니다. 래퍼를 만들어야하는 경우 (예 : bash-pf방금 만든 exec /bin/bash -o pipefail "$@"다음 #!줄 에 넣으면 효과 가 있습니다.) 참조 : en.wikipedia.org/wiki/Shebang_%28Unix%29
lindes

22

가능한 경우 종료 코드를 foo에 입력하는 것 bar입니다. 예를 들어 foo숫자가있는 줄 을 절대로 생산하지 않는다는 것을 알고 있다면 종료 코드를 사용할 수 있습니다.

{ foo; echo "$?"; } | awk '!/[^0-9]/ {exit($0)} {…}'

또는의 출력에 foo결코 다음과 같은 줄이 포함되어 있지 않다는 것을 알고 있다면 .:

{ foo; echo .; echo "$?"; } | awk '/^\.$/ {getline; exit($0)} {…}'

bar마지막 행을 제외한 모든 행에서 작업하고 종료 코드로 마지막 행을 전달할 수 있는 방법이 있으면 항상 수행 할 수 있습니다 .

경우 bar출력이 필요하지 않은 복잡한 파이프 라인, 당신은 다른 파일 기술자에 종료 코드를 인쇄하여 그 일부를 생략 할 수 있습니다.

exit_codes=$({ { foo; echo foo:"$?" >&3; } |
               { bar >/dev/null; echo bar:"$?" >&3; }
             } 3>&1)

이후 $exit_codes일반적으로 foo:X bar:Y,하지만 될 수 bar:Y foo:X있으면 bar모든 입력 읽기 전에 또는 당신이 운이 있다면 종료됩니다. 최대 512 바이트의 파이프에 대한 쓰기는 모든 유니스에서 원자 적이므로 태그 문자열이 507 바이트 미만 이면 foo:$?bar:$?부분이 혼합되지 않습니다.

의 출력을 캡처해야하는 경우 bar어려워집니다. bar종료 코드 표시처럼 보이는 행을 포함하지 않는 출력을 정렬하여 위의 기술을 결합 할 수는 있지만 어리석게 나타납니다.

output=$(echo;
         { { foo; echo foo:"$?" >&3; } |
           { bar | sed 's/^/^/'; echo bar:"$?" >&3; }
         } 3>&1)
nl='
'
foo_exit_code=${output#*${nl}foo:}; foo_exit_code=${foo_exit_code%%$nl*}
bar_exit_code=${output#*${nl}bar:}; bar_exit_code=${bar_exit_code%%$nl*}
output=$(printf %s "$output" | sed -n 's/^\^//p')

물론 임시 파일 을 사용하여 상태를 저장 하는 간단한 옵션이 있습니다. 단순하지만 프로덕션 에서는 그렇게 간단 하지 않습니다 .

  • 여러 스크립트가 동시에 실행 중이거나 동일한 스크립트가 여러 곳에서이 방법을 사용하는 경우 다른 임시 파일 이름을 사용해야합니다.
  • 공유 디렉토리에 임시 파일을 안전하게 작성하는 것은 어렵습니다. 종종 /tmp스크립트가 파일을 작성할 수있는 유일한 곳입니다. mktempPOSIX는 아니지만 현재 모든 심각한 유니스에서 사용할 수있는을 (를) 사용하십시오 .
foo_ret_file=$(mktemp -t)
{ foo; echo "$?" >"$foo_ret_file"; } | bar
bar_ret=$?
foo_ret=$(cat "$foo_ret_file"; rm -f "$foo_ret_file")

1
임시 파일 접근 방식을 사용하는 경우 나는 어떤 쓰레기가 남아있을 수 있도록 스크립트가 죽었을 경우에도 모든 임시 파일을 제거 EXIT에 대한 트랩을 추가하는 것을 선호
miracle173

17

파이프 라인에서 시작 :

foo | bar | baz

다음은 POSIX 셸만 사용하고 임시 파일은 사용하지 않는 일반적인 솔루션입니다.

exec 4>&1
error_statuses="`((foo || echo "0:$?" >&3) |
        (bar || echo "1:$?" >&3) | 
        (baz || echo "2:$?" >&3)) 3>&1 >&4`"
exec 4>&-

$error_statuses 실패한 프로세스의 상태 코드를 임의의 순서로 색인과 함께 포함하여 각 명령을 생성 한 명령을 알려줍니다.

# if "bar" failed, output its status:
echo "$error_statuses" | grep '1:' | cut -d: -f2

# test if all commands succeeded:
test -z "$error_statuses"

# test if the last command succeeded:
! echo "$error_statuses" | grep '2:' >/dev/null

$error_statuses내 테스트에서 따옴표를 주목하십시오 . 그렇지 않으면 grep개행 문자가 공백으로 강제 변환되므로 구분할 수 없습니다.


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에서 command1의 종료 코드를 실행하고 인쇄하지만 stdout은 파일 기술자 3.

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

마지막 마술은 exec 4>&1우리가 먼저 별도의 명령으로 수행 한 것입니다. 외부 디스크의 stdout의 복사본으로 파일 디스크립터 4를 엽니 다. 명령 대체는 내부에있는 명령의 관점에서 표준에 기록 된 내용을 캡처하지만 명령 대체에 관한 한 command2의 출력은 파일 설명자 4로 이동하므로 명령 대체는이를 캡처하지 않습니다. 명령 대체에서 "아웃"되면 스크립트의 전체 파일 디스크립터 1로 계속 진행됩니다.

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

명령의 출력이 서로 뛰어 넘는 것처럼 덜 기술적이고 더 유쾌한 방법으로 볼 수 있습니다 : command1이 command2로 파이프 된 다음 printf의 출력이 명령 2 위로 건너 뛰면 command2가 catch하지 않습니다. 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에 직접 코드를 작성한다고 생각합니다. 추측 (파일 디스크립터가 열려 있는지 확인하고 열려있는 경우 사용하거나 그렇지 않으면 다르게 동작하는 프로그램을 상상할 수 있습니다). 따라서 후자는 아마도 명심하고 일반적인 경우에 사용하는 것이 가장 좋습니다.


흥미로워 보이지만이 명령이 무엇을 기대하는지 알 수 없으며 컴퓨터도 마찬가지입니다. 나는 얻는다 -bash: 3: Bad file descriptor.
G-Man

@ G-Man 맞아, bash는 내가 일반적으로 사용하는 쉘 (통화 상자와 함께 제공되는 재)과 달리 파일 디스크립터와 관련하여 무엇을하고 있는지 전혀 모른다. bash를 행복하게 만드는 해결 방법을 생각하면 알려 드리겠습니다. 그동안 데비안 박스를 가지고 있다면 대시로 시도해 보거나 busybox를 가지고 있으면 busybox ash / sh로 시도해 볼 수 있습니다.
mtraceur 2016 년

@ G-Man 명령이 수행 할 것으로 예상되는 작업과 다른 쉘에서 수행하는 작업은 command1에서 stdout을 리디렉션하여 명령 대체에 걸리지 않지만 명령 대체 외부에 있으면 삭제됩니다. fd3을 stdout으로 돌아가서 command2에 예상대로 파이프됩니다. command1이 종료되면 printf가 실행되고 종료 상태를 인쇄하며 이는 명령 대체에 의해 변수로 캡처됩니다. 여기에 매우 자세한 설명이 있습니다 : stackoverflow.com/questions/985876/tee-and-exit-status/… 또한, 당신의 의견은 마치 모욕적 인 것으로 읽힌 것입니까?
mtraceur 2016

어디서부터 시작해야합니까? (1) 당신이 모욕을 받았다면 미안합니다. “재미있는 것 같다”는 진지한 의미였습니다. 예상만큼 컴팩트 한 것이 작동하면 좋을 것입니다. 그 외에도, 나는 단순히 솔루션이 무엇을 해야하는지 이해하지 못했다고 말하고있었습니다. 나는에 대한 유닉스와 재생 / 일한지 오래 내가 뭔가를 이해하지 않는 경우, (전에 리눅스가 존재하기 때문에) 시간, 즉, 붉은 깃발의 아마, 다른 사람이 하나를 이해하지 못할, 그리고 그 자세한 설명이 필요합니다 (IMNSHO). … (계속)
G-Man

(계속) ... 이후 당신이 옵니다, "... [당신이] 그냥 보통 사람보다 모든 것을 이해하는 것이 ... 생각하고 싶다"어쩌면 당신은 스택 교환의 목적은 명령 쓰기 서비스를 할 아니라는 것을 기억해야한다 사소한 별개의 질문에 대한 수천 개의 일회성 솔루션; 오히려 사람들에게 자신의 문제를 해결하는 방법을 가르치기 위해. 그리고이를 위해“평균 사람”이 이해할 수있을만큼 충분히 설명해야 할 수도 있습니다. lesmana의 답변을 보면 훌륭한 설명의 예가 있습니다. … (계속)
G-Man


7

위의 lesmana 솔루션은 { .. }대신 중첩 하위 프로세스를 시작하는 오버 헤드없이 수행 할 수 있습니다 (이러한 그룹화 된 명령 형식은 항상 세미콜론으로 끝나야 함을 기억하십시오). 이 같은:

{ { { { someprog; echo $? >&3; } | filter >&4; } 3>&1; } | stdintoexitstatus; } 4>&1

대시 버전 0.5.5 및 bash 버전 3.2.25 및 4.2.42 로이 구성을 확인 했으므로 일부 쉘이 { .. }그룹화를 지원하지 않더라도 여전히 POSIX 호환입니다.


이것은 NetBSD sh, pdksh, mksh, dash, bash를 포함하여 시도한 대부분의 쉘에서 실제로 잘 작동합니다. 그러나 set -o pipefailksh 또는 여러 가지 wait명령을 사용 하더라도 AT & T Ksh (93s +, 93u +) 또는 zsh (4.3.9, 5.2)에서는 작동하지 않습니다. 서브 쉘 사용을 고집하는 것처럼 적어도 부분적으로 ksh의 구문 분석 문제 일 수 있지만 ifksh의 서브 쉘 변형을 선택하지만 다른 명령의 복합 명령을 남겨두면 실패합니다. .
그렉 에이 우즈

4

이식성이 뛰어납니다. 즉, 모든 POSIX 호환 쉘에서 작동하며, 현재 디렉토리를 쓸 수 있어야하며 동일한 트릭을 사용하는 여러 스크립트를 동시에 실행할 수 있습니다.

(foo;echo $?>/tmp/_$$)|(bar;exit $(cat /tmp/_$$;rm /tmp/_$$))

편집 : 여기 Gilles의 의견에 따른 강력한 버전이 있습니다.

(s=/tmp/.$$_$RANDOM;((foo;echo $?>$s)|(bar)); exit $(cat $s;rm $s))

Edit2 : 그리고 여기에 dubiousjim 주석에 따라 약간 가벼운 변형이 있습니다.

(s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s))

3
여러 가지 이유로 작동하지 않습니다. 1. 임시 파일은 쓰기 전에 읽을 수 있습니다. 2. 예측 가능한 이름으로 공유 디렉토리에 임시 파일을 작성하는 것은 안전하지 않습니다 (사소한 DoS, symlink race). 3. 동일한 스크립트가이 트릭을 여러 번 사용하는 경우 항상 동일한 파일 이름을 사용합니다. 1을 해결하려면 파이프 라인이 완료된 후 파일을 읽으십시오. 2와 3을 해결하려면 무작위로 생성 된 이름을 가진 임시 파일 또는 개인 디렉토리를 사용하십시오.
Gilles

+1 글쎄 $ {PIPESTATUS [0]}은 더 쉽지만 Gilles가 언급 한 문제에 대해 알고 있다면 기본 아이디어가 효과적입니다.
Johan

몇 개의 서브 쉘을 저장할 수 있습니다 : (s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s)). @Johan : Bash를 사용하는 것이 더 쉽다는 데 동의하지만 일부 상황에서는 Bash를 피하는 방법을 아는 것이 좋습니다.
dubiousjim

4

다음은 일반적인 솔루션 중 하나를 사용할 수없는 경우 @Patrik의 답변에 대한 애드온을 의미합니다.

이 답변은 다음을 가정합니다.

  • 당신은 모르 $PIPESTATUS거나 껍질이set -o pipefail
  • 병렬 실행에 파이프를 사용하려고하므로 임시 파일이 없습니다.
  • 스크립트가 중단되면 갑작스런 정전으로 인해 추가적인 혼란을 피하고 싶지 않습니다.
  • 이 솔루션은 비교적 읽기 쉽고 읽기 편해야합니다.
  • 추가 서브 쉘을 도입하고 싶지 않습니다.
  • 기존 파일 디스크립터를 피들 링 할 수 없으므로 stdin / out / err를 건드리지 않아야합니다 (단, 새로운 파일 디스크립터를 일시적으로 도입 할 수는 있음)

추가 가정. 당신은 모든 것을 제거 할 수 있지만, 이것은 조리법을 너무 많이 방해하므로 여기에서는 다루지 않습니다.

  • PIPE의 모든 명령에 종료 코드 0이 있다는 것만 알면됩니다.
  • 추가 측 파대 정보가 필요하지 않습니다.
  • 쉘은 모든 파이프 명령이 리턴되기를 기다립니다.

Before : foo | bar | baz하지만 마지막 명령의 종료 코드 만 반환합니다 ( baz).

구함 : 파이프의 명령 중 하나라도 실패한 경우 (참)이 $?아니어야합니다 .0

후:

TMPRESULTS="`mktemp`"
{
rm -f "$TMPRESULTS"

{ foo || echo $? >&9; } |
{ bar || echo $? >&9; } |
{ baz || echo $? >&9; }
#wait
! read TMPRESULTS <&8
} 9>>"$TMPRESULTS" 8<"$TMPRESULTS"

# $? now is 0 only if all commands had exit code 0

설명 :

  • 을 사용하여 임시 파일이 생성됩니다 mktemp. 이것은 보통 즉시 파일을 만듭니다/tmp
  • 그런 다음이 임시 파일은 쓰기를 위해 FD 9로, 읽기를 위해 FD 8로 리디렉션됩니다.
  • 그런 다음 임시 파일이 즉시 삭제됩니다. 그러나 두 FD가 모두 사라질 때까지 열려 있습니다.
  • 이제 파이프가 시작되었습니다. 오류가있는 경우 각 단계는 FD 9에만 추가됩니다.
  • wait위해 필요 ksh하기 때문에, ksh모든 파이프가 종료 명령에 대한 다른 기다리지 않습니다. 그러나 일부 백그라운드 작업이 있으면 원하지 않는 부작용이 있으므로 기본적으로 주석 처리했습니다. 대기가 아프지 않으면 주석을 달 수 있습니다.
  • 그 후 파일 내용을 읽습니다. 비어 있으면 (모든 작업했기 때문에)이 read반환 false하므로 true오류를 나타냅니다.

이것은 단일 명령의 플러그인 대체물로 사용될 수 있으며 다음과 같은 것만 필요합니다.

  • 사용하지 않은 FD 9 및 8
  • 임시 파일의 이름을 보유하는 단일 환경 변수
  • 그리고이 레시피는 IO 리디렉션을 허용하는 거의 모든 쉘에 적용 할 수 있습니다
  • 또한 상당히 플랫폼에 독립적이며 다음과 같은 것들이 필요하지 않습니다. /proc/fd/N

버그 :

이 스크립트에는 /tmp공간이 부족한 경우 버그가 있습니다. 이 인공 사례에 대한 보호가 필요한 경우 다음과 같이 수행 할 수 있지만 이는 파이프의 명령 수 0000따라의 수가 달라 지므로 약간 더 복잡하다는 단점이 있습니다.

TMPRESULTS="`mktemp`"
{
rm -f "$TMPRESULTS"

{ foo; printf "%1s" "$?" >&9; } |
{ bar; printf "%1s" "$?" >&9; } |
{ baz; printf "%1s" "$?" >&9; }
#wait
read TMPRESULTS <&8
[ 000 = "$TMPRESULTS" ]
} 9>>"$TMPRESULTS" 8<"$TMPRESULTS"

휴대 성 노트 :

  • ksh마지막 파이프 명령 만 기다리는 비슷한 쉘은 wait주석 처리되지 않은

  • 마지막 예제는 이식성이 뛰어 나기 때문에 printf "%1s" "$?"대신 사용 합니다 echo -n "$?". 모든 플랫폼이 -n올바르게 해석되는 것은 아닙니다 .

  • printf "$?"실제로 그렇게 할 수도 있지만 printf "%1s"실제로 깨진 플랫폼에서 스크립트를 실행할 경우를 대비하여 코너 사례를 포착합니다. (읽기 :에서 프로그램하는 경우 paranoia_mode=extreme)

  • FD 8 및 FD 9는 여러 자리를 지원하는 플랫폼에서 더 높을 수 있습니다. POSIX 호환 쉘 뒤에는 한 자리 만 지원하면됩니다.

  • 데비안 8.2에서 테스트되었으며 sh, bash, ksh, ash, sash, 심지어csh


3

약간의주의를 기울이면 다음과 같이 작동합니다.

foo-status=$(mktemp -t)
(foo; echo $? >$foo-status) | bar
foo_status=$(cat $foo-status)

jlliagre와 같은 정리는 어떻습니까? foo-status라는 파일을 남기지 않습니까?
Johan

@Johan : 내 제안을 선호하는 경우 투표를 주저하지 말고;) 파일을 남기지 말고 여러 프로세스에서 동시에 실행할 수 있다는 장점이 있으며 현재 디렉토리를 쓸 수 있어야합니다.
jlliagre

2

다음 'if'블록은 'command'가 성공한 경우에만 실행됩니다.

if command; then
   # ...
fi

구체적으로 말하면 다음과 같이 실행할 수 있습니다.

haconf_out=/path/to/some/temporary/file

if haconf -makerw > "$haconf_out" 2>&1; then
   grep -iq "Cluster already writable" "$haconf_out"
   # ...
fi

haconf -makerwstdout 및 stderr을 실행 하여 "$ haconf_out"에 저장합니다. from에서 반환 된 값 haconf이 true이면 'if'블록이 실행되고 grep"$ haconf_out"을 읽고 "Cluster already writable"과 일치 시키려고합니다.

파이프가 자동으로 청소됩니다. 리디렉션을 마치면 "$ haconf_out"을 제거 할 때주의해야합니다.

pipefail이 기능이 도달 할 수없는 경우 우아 하지는 않지만 합법적 인 대안입니다.


1
Alternate example for @lesmana solution, possibly simplified.
Provides logging to file if desired.
=====
$ cat z.sh
TEE="cat"
#TEE="tee z.log"
#TEE="tee -a z.log"

exec 8>&- 9>&-
{
  {
    {
      { #BEGIN - add code below this line and before #END
./zz.sh
echo ${?} 1>&8  # use exactly 1x prior to #END
      #END
      } 2>&1 | ${TEE} 1>&9
    } 8>&1
  } | exit $(read; printf "${REPLY}")
} 9>&1

exit ${?}
$ cat zz.sh
echo "my script code..."
exit 42
$ ./z.sh; echo "status=${?}"
my script code...
status=42
$

0

(적어도 bash로) set -e하나 와 결합 하면 subshell을 사용하여 pipefail을 명시 적으로 에뮬레이트하고 파이프 오류에서 종료 할 수 있습니다

set -e
foo | bar
( exit ${PIPESTATUS[0]} )
rest of program

따라서 foo어떤 이유로 든 실패하면 나머지 프로그램은 실행되지 않고 스크립트는 해당 오류 코드와 함께 종료됩니다. (이것은 foo오류의 원인을 이해하기에 충분한 자체 오류 를 인쇄 한다고 가정합니다 )


-1

편집 :이 답변은 잘못되었지만 흥미로워 나중에 참조 할 수 있도록 남겨 두겠습니다.


!명령에 a 를 추가 하면 리턴 코드가 반전됩니다.

http://tldp.org/LDP/abs/html/exit-status.html

# =========================================================== #
# Preceding a _pipe_ with ! inverts the exit status returned.
ls | bogus_command     # bash: bogus_command: command not found
echo $?                # 127

! ls | bogus_command   # bash: bogus_command: command not found
echo $?                # 0
# Note that the ! does not change the execution of the pipe.
# Only the exit status changes.
# =========================================================== #

1
나는 이것이 관련이 없다고 생각합니다. 귀하의 예에서, 종료 코드를 알고 싶습니다. 종료 코드를 ls반전시키지 마십시오bogus_command
Michael Mrozek

2
나는 그 대답을 다시 그리는 것이 좋습니다.
maxschlepzig 2018 년

3
내가 바보 인 것 같아 나는 OP가 원하는 것을하기 전에 스크립트에서 실제로 이것을 사용했습니다. 내가 중요한 것을 위해 그것을 사용하지 않은 좋은 것
Falmarri
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.