ソースコードを考えてみましょう:
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
。 - 2番目のケースでは、
$exit_status
ティーの終了ステータスがキャプチャされます0
。
もしそうなら、終了ステータスをキャプチャしてteeを使用する方法は?
答え1
再印刷(および改善)comp.unix.shell FAQ(私はFAQのこの部分を書いたからです):
cmd1 | cmd2からcmd1の終了コードを取得する方法
まず、cmd1の終了コードがゼロではないかもしれませんが、これはエラーを意味しません。たとえば、このような場合が発生します。
cmd | head -n 1
141(またはksh93の場合は269、yashの場合は397)の終了状態を見ることができますが、cmd
これは1行を読んだ後に終了するのが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
、 にあり、一番右のゼロ以外の終了状態$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