1
標準出力がパイプにリダイレクトされるPOSIXシェルスクリプトがあります。スクリプトの実行中にある時点でパイプが壊れる可能性があります。
だから私はこれを試しました:
(
trap "" PIPE # prevent shell from terminating due to SIGPIPE
while :; do
echo trying to write to stdout >&2
echo writing something to stdout || break
echo successfully written to stdout >&2
sleep 1
done
echo continuing here after loop >&2
) | sleep 3
以下を印刷します。
trying to write to stdout
successfully written to stdout
trying to write to stdout
successfully written to stdout
trying to write to stdout
successfully written to stdout
trying to write to stdout
sh: 5: echo: echo: I/O error
continuing here after loop
この例では、sleep
スクリプトを使用してプログラムの標準出力を置き換えます。 3秒後にsleep
終了し、パイプが切断されます。
私たちはstdoutだけをパイピングするので、sleep
stderrを使用してその間にいくつかのデバッグメッセージを処理できます。
規制によると、破損したパイプに書き込むとSIGPIPEが発生し、デフォルトの動作はプログラムを終了することです。POSIXsignal.h
。そのため、私たちはtrap
信号を受け入れて無視しなければなりません。
終了時にsleep
パイプが中断され、続いてecho writing something to stdout
SIGPIPEが発生して捕捉(無視)され、echo
失敗して|| break
ループを終了します。スクリプトは問題なく実行され続けます。
だから上記の例はうまくいきます。明らかな主な欠点は、パイプラインがまだ機能していることを確認するために「stdoutに書き込む」の多くを使用してパイプラインにスパムを送信することです。 「書き込み」パイプecho writing something to stdout
と交換すると、printf ""
パイプが損傷してから古いにもかかわらず、SIGPIPEが発生せずにループが続行されます。
どうですか?
答え1
スクリプトの実行中にどの時点でパイプが壊れ、いつこのようなことが起こるのかを知りたいです。
パイプに書き込もうとするときにのみこれがわかります。
write()
Linuxのマニュアルページによると、リーダーなしでパイプの書き込み側へのすべての呼び出しは、ゼロバイトが書き込まれても信号/エラーを提供する必要があるようです。しかし、私が試しているシェルは、印刷するものがなければシステムコール全体をスキップするので役に立ちません。
ゼロ以外の量のデータを書き込む場合は、書き込み中にある時点でスクリプトがブロックされることがわかります。つまり、リーダがジョブの完了を無視し、パイプバッファがいっぱいになる場合です。
そしてまたコメントでこう言われました。
基本的にシェルスクリプトで選択/ポーリングを使用したいと思います。
...この場合、実際にはシェルから正しいプログラミング言語に切り替える必要があります。または、zselect
フロントエンドとして使用できるモジュールがあるZshに切り替えてくださいselect()
。https://zsh.sourceforge.io/Doc/Release/Zsh-Modules.html#The-zsh_002fzselect-Module
select()
パイプの読み取り端が閉じたときに見つけるのに役立たないと確信しています。
答え2
少なくともLinuxとFreeBSDでは、poll()
マスクを使用してPOLLERR
壊れたパイプを検出できます。
poll()
POSIXツールボックスにはCLIインターフェイスはありませんが、通常使用可能なインターフェイスを使用できます(またはperl
他の多くのPOSIXユーティリティとは対照的に)。pax
bc
m4
perl -MIO::Poll -e '$p=IO::Poll->new; $p->mask(STDOUT,POLLERR); $p->poll'
標準出力のパイプが壊れた場合に返されます。
SSHクライアントがシャットダウンしたときにリモートコマンドを終了するユースケース:
ssh host '
exec perl -MIO::Poll -we '\''
$SIG{CHLD} = sub{wait; exit($? & 127 ? 128|($?&127) : $?>>8)};
exec "sleep 3600 # example" unless fork;
$p = IO::Poll->new;
$p->mask(STDOUT, POLLERR);
$p->poll;
kill "HUP", 0'\'
/proc/$pid/fd/$fd
Linuxでは、誰かが読み取りまたは読み取り+書き込みモードでパイプを開くと、中断することなくパイプが破損する可能性があります。ここでは、$fd
書き込みモードでパイプを開いたプロセスのfdです。$pid
$ exec 3> >(:)
$ perl -MIO::Poll -e '$p=IO::Poll->new; $p->mask(STDOUT,POLLERR); $p->poll' >&3 && echo broken
broken
$ exec 4< /dev/fd/3
$ echo unbroken >&3
$ cat <&4
unbroken
私の考えでは、状況を調査するのではなく、それを受け入れて対処する方が良いと思います。
シェルの場合、以下がprintf
組み込まれています。
(
trap 'echo>&2 Pipe is broken' PIPE
while printf 'Whatever\n'; do
sleep 1
done
) | sleep 5
SIGPIPE が処理されます。printf
組み込まれていない場合、それを実行するプロセスはSIGPIPEのために終了します。終了ステータスに基づいて確認できます[ "$(kill -l "$?") = PIPE ]
。
たとえば、SIGPIPEを無視すると、破損したtrap '' PIPE
パイプに書き込むと、プロセス(サブプロセスを含む)がSIGPIPEを受け取ることができませんが、write()
まだ失敗しますEPIPE
(エラーは通常プロセスを終了して処理されます)。
編集する
@TheDiveOが指摘したようにこの同様の質問、Linux select()
(およびFreeBSDも同様)は、オープンfd(書き込み専用モードでも)が破損している場合(監視されたfdリストにある場合)にパイプに返します。読む。
$ zmodload zsh/zselect
$ (zselect -r 1; echo>&2 done) | sleep 1
done
したがって、ログインシェルがzshのマシンにsshingしている場合は、次のことができます。
ssh host '
zmodload zsh/zselect
cmd 3> >(zselect -r 0 -r 1; kill -s HUP 0)'
zselect
cmd 終了を検出するために、stdin (cmd の fd 3 のパイプ) に EOF が表示されるか、stdout で破損したパイプが検出された場合に返されます。次に、cmd
サブシェルとシェルを置き換えるまだ実行中のプロセスを含むプロセスグループ全体(0)を終了します。
答え3
tail
書かなくても、オペレーティングシステムは標準出力がパイプが壊れているかどうかを知らないかもしれません。バラよりこの回答到着tail -f … | grep -q …
一致するものを見つけても終了しないのはなぜですか?
最新バージョンはtail
GNU Coreutilsで入手できます。
もしあなたはtail
本当にスマートです。もしstdoutがパイプであることを確認し、スクリプトtail -f /dev/null
で実行します。パイプが切断されるとすぐにコマンドが終了します。
概念証明(tail
例:GNU Coreutilsの「smart」が必要):
sh -c 'tail -f /dev/null; echo >&2 "Pipe broken!"' | sleep 5
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this is our script
# ^ this pipe will break after 5 seconds
メモ:
tail -f /dev/null
何も印刷されません。- stdoutが通常のファイルの場合
tail -f /dev/null
、いいえ常に自分で終了してください。 - 私は
tail
GNU Coreutils 8.32を使ってKubuntu 22.10でテストしました。 - 比較してみてください。
busybox tail -f /dev/null
「スマート」せずにパイプが破損した後もそのまま残ります。
答え4
私の考えでは、そのチューブが壊れそうにない。 BashでOR演算子(||)を使用すると、通常は条件文(if文)で使用されるため、ほとんど常に無視されます。
このプログラムが他のプログラムのテストである場合は、ループを使用することをお勧めしますfor
。
char='1 2 3 4 5' # Change this to whatever you want
for i in $char; do
printf "Something"
done
範囲を作成することもできます。
for i in {1..[your number]}; do
printf "Something"
done
お役に立てば幸いです。頑張ってください!