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

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

파이프를 통해 연결된 두 개의 프로세스 foo및 가 있습니다 .bar

$ foo | bar

bar항상 0으로 종료됩니다. 종료 코드에 관심이 있습니다 foo. 그것을 얻을 수 있는 방법이 있나요?

답변1

bash그리고 zsh쉘이 실행한 마지막 파이프라인의 각 요소(명령)의 종료 상태를 보유하는 배열 변수가 있습니다.

를 사용하면 bash배열이 호출되고 PIPESTATUS(대소문자 구분!) 배열 인덱스는 0부터 시작됩니다.

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

를 사용하면 zsh배열이 호출되고 pipestatus(대소문자 구분!) 배열 인덱스는 1에서 시작합니다.

$ 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위의 코드를 or 에서 실행하면 and 중 하나만 zsh설정하면 동일한 결과를 얻을 수 있습니다 retval_bash. retval_zsh다른 하나는 비어 있습니다. 이렇게 하면 함수가 다음으로 끝날 수 있습니다 return $retval_bash $retval_zsh(따옴표가 없음에 유의하세요!).

답변2

이를 달성하는 세 가지 일반적인 방법이 있습니다.

파이프 고장

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

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

$파이프라인 상태

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

답변3

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

상태:

someprog | filter

원하는 종료 상태 someprog및 출력 filter.

이것이 내 해결책입니다.

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

이 구성의 결과는 filter생성자의 stdout의 stdout이고 someprog생성자의 종료 상태의 종료 상태입니다.


{...}이 구조는 서브쉘이 아닌 간단한 명령 그룹화에도 작동합니다 (...). 서브셸에는 여기서는 필요하지 않은 성능 비용을 포함하여 몇 가지 의미가 있습니다. 자세한 내용은 nice 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한 번만 읽히므로 무시됩니다.

파일 설명자 3이나 4에 쓸 가능성이 우려되는 경우 를 someprog호출하기 전에 파일 설명자를 닫는 것이 가장 좋습니다 someprog.

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

before는 실행 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. stdout으로 리디렉션된 파일 설명자 4를 사용하여 하위 쉘을 만듭니다. 이는 서브쉘의 파일 설명자 4에 인쇄된 모든 내용이 전체 구성에 대한 표준 출력으로 종료됨을 의미합니다.
  2. 파이프라인을 생성하고 #part3왼쪽( )과 오른쪽( )의 명령을 실행합니다. 또한 파이프라인의 마지막 명령은 stdin의 문자열이 전체 구성의 종료 상태가 됨을 의미합니다.#part2exit $xs
  3. stdout으로 리디렉션된 파일 설명자 3을 사용하여 하위 쉘을 만듭니다. 이는 이 서브쉘의 파일 설명자 3에 인쇄된 모든 내용이 결국 #part2전체 구성의 종료 상태가 됨을 의미합니다.
  4. 파이프를 생성하고 왼쪽( #part5#part6)과 오른쪽()의 명령을 실행합니다. filter >&4출력은 filter파일 설명자 4로 리디렉션됩니다. #part1파일 설명자 4의 stdout으로 리디렉션됩니다 . 이는 출력이 filter전체 구성의 표준 출력임을 의미합니다.
  5. 종료 상태는 #part6파일 설명자 3에 인쇄됩니다. #part3파일 설명자 3 에서 로 리디렉션합니다 #part2. 이는 종료 상태가 #part6전체 구성의 최종 종료 상태가 됨을 의미합니다.
  6. someprog처형되다. 종료 상태가 수신되었습니다 #part5. 표준 출력은 파이프 입력으로 얻어지고 #part4로 전달됩니다 filter. 에 설명된 대로 will 의 출력은 filter차례로 표준 출력으로 이동합니다.#part4

답변4

가능하다면 종료 코드 foobar.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발생할 수 있습니다 . 내 생각에 최대 512바이트까지 쓰는 모든 unice 파이프는 원자적이므로 태그 문자열이 507바이트 미만이면 및 부분이 섞이지 않습니다.bar:Y foo:Xbarfoo:$?bar:$?

캡처된 출력이 필요한 경우 bar어려워집니다. 종료 코드가 나타내는 것처럼 보이는 행을 포함 하도록 never의 출력을 정렬하여 위의 기술을 결합할 수 있지만 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이는 스크립트가 파일에 쓸 수 있는지 확인하는 유일한 위치입니다. 사용mktemp, 이는 POSIX는 아니지만 현재 모든 심각한 유니스에서 사용할 수 있습니다.
foo_ret_file=$(mktemp -t)
{ foo; echo "$?" >"$foo_ret_file"; } | bar
bar_ret=$?
foo_ret=$(cat "$foo_ret_file"; rm -f "$foo_ret_file")

관련 정보