変数 "PIPESTATUS" がdash
.simple にあるようです。一人で実行左のコマンドは非常に大きな出力を生成するので、動作しません。私はこれを行うためにfifoを使います。
#!/bin/dash
mkfifo command1 command2
dash -c "cat ./content;code=\${?};echo \${code} > command1 &" | dash -c "md5sum;code=\${?};echo \${code} > command2 &"
echo "$(cat ./command1)" "$(cat ./command2)"
しかし、なぜ壊れたのかわかりませんか?
答え1
名前付きパイプを使用して両方のプロセスを手動で接続できます。逆順で開始すると、左が前景で実行され、通常どおり$?
終了状態が得られます。
#!/bin/sh
dir=$(mktemp -d)
mkfifo "$dir/p"
cat < "$dir/p" > /dev/null &
( echo foo; exit 12; ) > "$dir/p"
echo "exit status: left: $?"
rm -r "$dir"
または、両方が必要な場合は、バックグラウンドプロセスのPIDを取得し、$!
終了wait
ステータスを取得します。
#!/bin/sh
dir=$(mktemp -d)
mkfifo "$dir/p"
( echo foo; exit 12; ) > "$dir/p" & # left-hand side
pidleft=$!
( cat; exit 34; ) < "$dir/p" > /dev/null & # right-hand side
pidright=$!
wait "$pidleft"; exitleft=$?
wait "$pidright"; exitright=$?
echo "exit status: left: $exitleft right: $exitright"
rm -r "$dir"
パイプの2番目の部分は前景にそのまま残すことができます。私はただ対称的にしたかっただけです。
終了ステータスをファイルに保存し、そこからインポートすることもできます。
#/bin/sh
( somecmd; echo "$?" > exit1 ) | ( cat; echo "$?" > exit2)
echo "exit status: left: $(cat exit1) right: $(cat exit2)"
終了状態はほんの数バイトしかないので、ここでは名前付きパイプはあまり使われていないと思います。シェルは、exit1
2行目で読み取りを試みる前にパイプが完了するのを待ちます。exit2
名前付きパイプを使用するには、読み取り側が開くまでパイプブロックに書き込むため、パイプをバックグラウンドに配置する必要があります。
#/bin/sh
mkfifo exit1 exit2
( somecmd; echo "$?" > exit1 ) | ( cat; echo "$?" > exit2) &
echo "exit status: left: $(cat exit1) right: $(cat exit2)"
ただし、cat
パイプを読み取るsが何らかの理由で実行されない場合、パイプを書き込むサブシェルはバックグラウンドで無期限にブロックされます。
答え2
実際のコマンドが終了したら、パイプの左側のstdoutと右側のstdinを閉じて、中断されたスクリプトをキャンセルできます。
例:
#! /bin/dash
rm -f /tmp/s1 /tmp/s2
mkfifo /tmp/s1 /tmp/s2
{ (echo yes; exit 13); s1=$?; exec >&-; echo $s1 >/tmp/s1 & } | { (cat; exit 17); s2=$?; exec <&-; echo $s2 >/tmp/s2 & }
echo "`cat /tmp/s1` `cat /tmp/s2`"
(...; exit ..)
そのコマンドに置き換えてください。
実際のコマンドが終了した後、パイプの右側のstdinを閉じると、右側でコマンドをパイプしようとしている間、ブロックの代わりに左側のwrite()がSIGPIPEまたはEPIPEを受け取ります(それ自体はopen()でブロックされます)。そして、左側のstdoutを閉じると、echo ... >fifo &
左側のブロックでパイプを試みるのではなく、パイプの右側のread()がEOFを受け取ります。echo ... >fifo &
修正してくれた@ilkkachuに感謝します。