나는 두 개의 프로세스가 foo
와 bar
파이프 연결을 :
$ foo | bar
bar
항상 0을 종료합니다. 의 종료 코드에 관심이 foo
있습니다. 그것을 얻을 수있는 방법이 있습니까?
답변
을 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
(따옴표가 없습니다!).
답변
이 작업을 수행하는 일반적인 3 가지 방법이 있습니다.
파이프 페일
첫 번째 방법은 pipefail
옵션 ( ksh
, zsh
또는 bash
) 을 설정하는 것 입니다. 이것은 가장 간단하며 기본적으로 종료 상태 $?
를 마지막 프로그램의 종료 코드로 설정하여 0이 아닌 종료합니다 (또는 모두 성공적으로 종료 된 경우 0).
$ false | true; echo $?
0
$ set -o pipefail
$ false | true; echo $?
1
$ PIPESTATUS
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
답변
이 솔루션은 bash 특정 기능이나 임시 파일을 사용하지 않고 작동합니다. 보너스 : 결국 종료 상태는 실제로 종료 상태이며 파일의 일부 문자열이 아닙니다.
상태:
someprog | filter
종료 상태 someprog
와 출력 을 원합니다 filter
.
내 해결책은 다음과 같습니다.
((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1
이 구성의 결과는 구성의 stdout에서 filter
stdout이고 구성의 종료 상태에서 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 문법에는 중괄호에 공백과 세미콜론이 필요하므로 구문이 훨씬 넓어집니다.
이 텍스트의 나머지 부분에서는 서브 쉘 변형을 사용합니다.
예 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
한 번만 읽기 때문에 실제 종료 상태는 무시 됩니다.
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
아래에서 위로 :
- 서브 쉘은 파일 디스크립터 4가 stdout으로 경로 재 지정되어 작성됩니다. 이것은 서브 쉘에서 파일 디스크립터 4에 인쇄 된 모든 것이 전체 구성의 표준 출력으로 끝나는 것을 의미합니다.
- 파이프가 생성되고 왼쪽 (
#part3
) 및 오른쪽 (#part2
) 의 명령 이 실행됩니다.exit $xs
또한 파이프의 마지막 명령이며 stdin의 문자열이 전체 구문의 종료 상태가됨을 의미합니다. - 파일 디스크립터 3이 stdout으로 경로 재 지정된 서브 쉘이 작성됩니다. 이것은이 서브 쉘에서 파일 디스크립터 3에 인쇄 된 것은 결국
#part2
전체 구성의 종료 상태 가됨을 의미합니다 . - 파이프가 생성되고 왼쪽 (
#part5
및#part6
) 및 오른쪽 (filter >&4
) 의 명령 이 실행됩니다. 의 출력filter
에서 설명 4. 파일로 재되는#part1
파일 디스크립터 4 표준 출력으로 리디렉션한다. 이것은 출력이filter
전체 구성의 표준 출력임을 의미합니다 . - 종료 상태
#part6
가#part3
파일 설명자 3으로 인쇄됩니다. 파일 설명자 3이 (가)로 리디렉션되었습니다#part2
. 이는#part6
종료 상태가 전체 구성에 대한 최종 종료 상태가 됨을 의미합니다 . someprog
실행됩니다. 종료 상태는입니다#part5
. stdout은 파이프로 가져와로#part4
전달됩니다filter
. 의 출력은filter
차례로 설명 된대로 표준 출력에 도달합니다.#part4
답변
정확히 당신이 요구 한 것은 아니지만 사용할 수 있습니다.
#!/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
답변
가능한 경우 종료 코드를 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
스크립트가 파일을 작성할 수있는 유일한 곳입니다.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")
답변
파이프 라인에서 시작 :
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
개행 문자가 공백으로 강제 변환되므로 구분할 수 없습니다.
답변
그래서 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에 직접 코드를 작성한다고 생각합니다. 추측 (파일 디스크립터가 열려 있는지 확인하고 열려있는 경우 사용하거나 그렇지 않으면 다르게 동작하는 프로그램을 상상할 수 있습니다). 따라서 후자는 아마도 명심하고 일반적인 경우에 사용하는 것이 가장 좋습니다.