Bash에서 장시간 실행되는 명령을 실행하고 종료 상태를 캡처 하고 출력을 티피 하고 싶습니다 .
그래서 나는 이것을한다 :
command | tee out.txt
ST=$?
문제는 변수 ST tee
가 명령이 아닌 종료 상태를 캡처한다는 것 입니다. 이 문제를 어떻게 해결할 수 있습니까?
명령을 오랫동안 실행하고 나중에 볼 수 있도록 출력을 파일로 리디렉션하는 것은 좋은 해결책이 아닙니다.
Bash에서 장시간 실행되는 명령을 실행하고 종료 상태를 캡처 하고 출력을 티피 하고 싶습니다 .
그래서 나는 이것을한다 :
command | tee out.txt
ST=$?
문제는 변수 ST tee
가 명령이 아닌 종료 상태를 캡처한다는 것 입니다. 이 문제를 어떻게 해결할 수 있습니까?
명령을 오랫동안 실행하고 나중에 볼 수 있도록 출력을 파일로 리디렉션하는 것은 좋은 해결책이 아닙니다.
답변:
내부 Bash 변수가 있습니다 $PIPESTATUS
; 마지막 포 그라운드 파이프 라인 파이프 라인에서 각 명령의 종료 상태를 유지하는 배열입니다.
<command> | tee out.txt ; test ${PIPESTATUS[0]} -eq 0
또는 다른 쉘 (zsh와 같은)에서도 작동하는 다른 대안은 pipefail을 활성화하는 것입니다.
set -o pipefail
...
첫 번째 옵션은 약간 다른 구문으로 인해 작동 하지 않습니다zsh
.
exit ${PIPESTATUS[0]}
.
bash를 사용하면 set -o pipefail
도움이됩니다.
pipefail : 파이프 라인의 반환 값은 0이 아닌 상태로 종료하는 마지막 명령의 상태이거나 0이 아닌 상태로 종료 된 명령이없는 경우 0입니다.
( set -o pipefail; command | tee out.txt ); ST=$?
set -o pipefail
하려면 명령을 수행 한 다음 즉시 수행 set +o pipefail
하고 옵션을 설정 해제하십시오.
-o pipefail
파이프가 실패 할 경우 자신이 알고있는 것이지만, '명령'과 '티'모두가 실패 할 경우, 그는 '티'에서 종료 코드를받을 것입니다.
벙어리 솔루션 : 명명 된 파이프 (mkfifo)를 통해 연결. 그런 다음 명령을 두 번째로 실행할 수 있습니다.
mkfifo pipe
tee out.txt < pipe &
command > pipe
echo $?
mkfifo
에는 mknod -p
내가 기억 하지 않으면 필요하지 않고 대신 필요할 수 있습니다 .
mkfifo
또는에 문제가있는 경우 mknod -p
: 내 경우에는 파이프 파일을 만드는 적절한 명령이었습니다 mknod FILE_NAME p
.
파이프에있는 각 명령의 종료 상태를 제공하는 배열이 있습니다.
$ 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
이 솔루션은 bash 특정 기능이나 임시 파일을 사용하지 않고 작동합니다. 보너스 : 결국 종료 상태는 실제로 종료 상태이며 파일의 일부 문자열이 아닙니다.
상태:
someprog | filter
종료 상태 someprog
와 출력 을 원합니다 filter
.
내 해결책은 다음과 같습니다.
((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1
echo $?
자세한 설명과 서브 쉘이없는 대안 및주의 사항 은 unix.stackexchange.com에서 동일한 질문에 대한 내 대답을 참조하십시오 .
서브 쉘 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
VALUE=$(might_fail | piping)
마스터 쉘에서 PIPESTATUS를 설정하지 않지만 오류 수준을 설정 하는 구문을 사용할 수있었습니다 . 사용하여 : VALUE=$(might_fail | piping; exit ${PIPESTATUS[0]})
나는 내가 원하는 것을 얻습니다.
command_might_fail | grep -v "line_pattern_to_exclude" || exit ${PIPESTATUS[0]}
티가 아니라 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에서 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에 직접 작성합니다. 추측 (파일 디스크립터가 열려 있는지 확인하고 열려있는 경우 사용하거나 그렇지 않으면 다르게 동작하는 프로그램을 상상할 수 있습니다). 따라서 후자는 아마도 명심하고 일반적인 경우에 사용하는 것이 가장 좋습니다.
파이프 명령이 반환 된 직후 PIPESTATUS [@]를 배열에 복사해야합니다. 어느 PIPESTATUS의 읽기 [@] 내용을 지 웁니다. 모든 파이프 명령의 상태를 확인하려면 다른 배열로 복사하십시오. "$?" "$ {PIPESTATUS [@]}"의 마지막 요소와 동일한 값이며, "$ {PIPESTATUS [@]}"을 (를) 파기하는 것으로 보이지만이를 완전히 확인하지는 않았습니다.
declare -a PSA
cmd1 | cmd2 | cmd3
PSA=( "${PIPESTATUS[@]}" )
파이프가 하위 셸에 있으면 작동하지 않습니다. 해당 문제점에 대한 솔루션 은 backticked 명령의 bash pipestatus를
참조하십시오 .
일반 bash에서이를 수행하는 가장 간단한 방법 은 파이프 라인 대신 프로세스 대체 를 사용 하는 것입니다. 몇 가지 차이점이 있지만 유스 케이스에는별로 중요하지 않습니다.
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에 물건을 계속 쓸 수 있으며, 이는 혼란 스러울 수 있습니다.
순수한 쉘 솔루션 :
% 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의 출력과 같은 경우) 로그 파일을 삭제할 수 있습니다.
@ 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
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
오류가 없으면 종료하지 않습니다. 두 번째 명령으로 실행되므로 반환하는 두 번째 명령입니다.