소스 코드를 고려해보세요:
1.부모.sh
#!/usr/bin/ksh
# No tee
ksh Child.sh;
exit_status=$?;
echo "Exit status: ${exit_status}"
# Using tee
ksh Child.sh | tee -a log.txt;
exit_status=$?;
echo "Exit status: ${exit_status}"
2.어린이.sh
#!/usr/bin/ksh
...
exit 1;
산출:
Exit status: 1
Exit status: 0
- 변수는
$exit_status
Child.sh의 종료 상태를 캡처하므로 입니다1
. - 두 번째 경우에는
$exit_status
tee의 종료 상태가 캡처됩니다0
.
그렇다면 종료 상태를 캡처하고 tee를 사용하는 방법은 무엇입니까?
답변1
재인쇄(및 개선)comp.unix.shell FAQ(왜냐면 제가 FAQ의 이 부분을 썼기 때문입니다):
cmd1|cmd2에서 cmd1의 종료 코드를 얻는 방법
먼저, cmd1 종료 코드가 0이 아닐 수 있지만 이는 오류를 의미하지 않습니다. 예를 들어, 이런 경우가 발생합니다.
cmd | head -n 1
141(또는 ksh93의 경우 269, yash의 경우 397) 종료 상태를 볼 수 있지만 cmd
이는 한 줄을 읽은 후 종료하는 것이 cmd
SIGPIPE 신호에 의해 중단되었기 때문입니다.head -n 1
파이프라인 요소의 종료 상태 이해
cmd1 | cmd2 | cmd3
zsh(및 Fish 3.1+) 사용:
종료 코드는 특수 배열로 제공됩니다 pipestatus
.
cmd1
종료 코드는 in $pipestatus[1]
이고 cmd3
종료 코드는 in
$pipestatus[3]
이므로 $status
/ 는 $?
항상 와 동일합니다
$pipestatus[-1]
.
배쉬와 함께:
종료 코드는 특수 배열로 제공됩니다 PIPESTATUS
. 종료 코드 는 에 있으므로
cmd1
항상 (또는 4.2 이전 버전의 경우) 동일합니다 .${PIPESTATUS[0]}
cmd3
${PIPESTATUS[2]}
$?
${PIPESTATUS[-1]}
${PIPESTATUS[@]: -1}
다른 Bourne형 쉘과 마찬가지로
종료 코드를 기본 셸에 전달하려면 트릭을 사용해야 합니다. 이는 파이프(2)를 사용하여 수행할 수 있습니다. 실행하는 대신 cmd1
실행하여 cmd1; echo "$?"
$?가 셸에 들어가는지 확인합니다.
exec 3>&1
code=`
# now, inside the backticks, fd4 goes to the pipe
# whose other end is read and stored in $code for
# later evaluation; fd1 is the normal standard output
# preserved the line before with exec 3>&1
exec 4>&1 >&3 3>&-
{
cmd1 4>&-; echo "ec1=$?;" >&4
} | {
cmd2 4>&-; echo "ec2=$?;" >&4
} | cmd3 4>&-
echo "ec3=$?;" >&4
`
exec 3>&-
eval "$code"
$ec1
, , $ec2
의 종료 코드입니다 $ec3
.
POSIX 쉘 사용
이 기능을 사용하면 작업을 더 쉽게 할 수 있습니다.
run() {
j=1
while eval "\${pipestatus_$j+:} false"; do
unset "pipestatus_$j"
j=$(($j+1))
done
j=1 com= k=1 l=
for arg do
case $arg in
('|')
com="$com {
$l "'3>&-
echo "pipestatus_'$j'=$?" >&3
} 4>&- |'
j=$(($j+1)) l=;;
(*)
l="$l \"\${$k}\""
esac
k=$(($k+1))
done
com="$com $l"' 3>&- >&4 4>&-
echo "pipestatus_'$j'=$?"'
{ eval "$(exec 3>&1; eval "$com")"; } 4>&1
j=1
ret=0
while eval "\${pipestatus_$j+:} false"; do
eval '[ "$pipestatus_'"$j"'" -eq 0 ] || ret=$pipestatus_'"$j"
j=$(($j+1))
done
return "$ret"
}
다음과 같이 사용하세요:
run cmd1 \| cmd2 \| cmd3
종료 코드는 $pipestatus_1
, $pipestatus_2
및 에 있으며 가장 오른쪽의 0이 아닌 종료 상태 $pipestatus_3
입니다 ( 일부 셸 옵션과 유사).$?
pipefail
답변2
bash에서는 && 및 ||를 사용하여 이 작업을 수행할 수 있습니다. ksh에서도 비슷한 작업이 가능할 것 같습니다.
ksh Child.sh && exit_status="$?" || exit_status="$?" | tee -a log.txt;
편집하다:
@Stephane이 지적했듯이 A && B | C
A는 stdout으로 출력되고 B만 C로 파이프됩니다. 출력을 그룹화하고 함께 파이핑해야 합니다. 서브셸에서 호출자에게 변수를 전달할 수는 없지만 다음과 같이 할 수 있습니다.
x=$(tempfile) && exit_status=$(ksh Child.sh > $x; echo $?) && (cat $x; rm $x) | tee -a log.txt