파이프를 통해 연결된 두 개의 프로세스 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
( $pipestatus
in ) 라는 배열 변수도 있습니다 .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 구문에는 구조가 더 넓어지도록 중괄호 안에 공백과 세미콜론이 필요합니다.
이 기사의 나머지 부분에서는 서브셸 변형을 사용하겠습니다.
예 someprog
및 filter
:
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
아래에서 위로:
- stdout으로 리디렉션된 파일 설명자 4를 사용하여 하위 쉘을 만듭니다. 이는 서브쉘의 파일 설명자 4에 인쇄된 모든 내용이 전체 구성에 대한 표준 출력으로 종료됨을 의미합니다.
- 파이프라인을 생성하고
#part3
왼쪽( )과 오른쪽( )의 명령을 실행합니다. 또한 파이프라인의 마지막 명령은 stdin의 문자열이 전체 구성의 종료 상태가 됨을 의미합니다.#part2
exit $xs
- stdout으로 리디렉션된 파일 설명자 3을 사용하여 하위 쉘을 만듭니다. 이는 이 서브쉘의 파일 설명자 3에 인쇄된 모든 내용이 결국
#part2
전체 구성의 종료 상태가 됨을 의미합니다. - 파이프를 생성하고 왼쪽(
#part5
및#part6
)과 오른쪽()의 명령을 실행합니다.filter >&4
출력은filter
파일 설명자 4로 리디렉션됩니다.#part1
파일 설명자 4의 stdout으로 리디렉션됩니다 . 이는 출력이filter
전체 구성의 표준 출력임을 의미합니다. - 종료 상태는
#part6
파일 설명자 3에 인쇄됩니다.#part3
파일 설명자 3 에서 로 리디렉션합니다#part2
. 이는 종료 상태가#part6
전체 구성의 최종 종료 상태가 됨을 의미합니다. someprog
처형되다. 종료 상태가 수신되었습니다#part5
. 표준 출력은 파이프 입력으로 얻어지고#part4
로 전달됩니다filter
. 에 설명된 대로 will 의 출력은filter
차례로 표준 출력으로 이동합니다.#part4
답변4
가능하다면 종료 코드 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
발생할 수 있습니다 . 내 생각에 최대 512바이트까지 쓰는 모든 unice 파이프는 원자적이므로 태그 문자열이 507바이트 미만이면 및 부분이 섞이지 않습니다.bar:Y foo:X
bar
foo:$?
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")