
最近、kshが数秒間ブロックされると、kshが16Kバイト以上を標準出力として印刷した後に一部のデータを失う可能性があることがわかりました。
このtest.sh
スクリプトは257 * 64(16448)バイトを印刷します。
#!/usr/bin/ksh
i=0
while [[ i -lt 257 ]]
do
x=$(file /tmp)
echo "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE"
i=$((i+1))
done |
while read datafile
do
echo $datafile
done
次のテストを実行しました。
0 $ ./test.sh | wc -c
16448
0 $ ./test.sh | (sleep 3; wc -c)
16384
この行はx=$(file /tmp)
2番目のループには何も送信しませんが、この動作に影響を与えるようです。
bashを使用すると、期待どおりに動作します。
私にとって、これはkshのバグのようです。私はSolaris 5.10を使用しています。解決策や回避策はありますか?この問題の根本原因は何ですか?私はこれがパイプバッファサイズに関連していると思います。
ありがとう、ピーター
編集する:
したがって、run testを使用すると、truss
最後の64バイトを書き込んだときにエラーが発生することがわかります。
ioctl(0, I_PEEK, 0x08046B40) = 0
Received signal #18, SIGCLD, in write() [caught]
siginfo: SIGCLD CLD_EXITED pid=6561 status=0x0000
write(1, " 0 1 2 3 4 5 6 7 8 9 A B".., 64) Err#4 EINTR
lwp_sigmask(SIG_SETMASK, 0x00020000, 0x00000000) = 0xFFBFFEFF [0x0000FFFF]
setcontext(0x08046670)
read(0, 0x0809064C, 1) = 0
ioctl(0, TCGETA, 0x08046B18) Err#22 EINVAL
dtkshを使用して同じスクリプトを実行する方法は次のとおりです。 Stephaneが指摘したように、失敗した書き込みは再試行されます。
ioctl(0, I_PEEK, 0x08046694) = 1
read(0, " 0 1 2 3 4 5 6 7 8 9 A B".., 64) = 64
Received signal #18, SIGCLD, in write() [caught]
siginfo: SIGCLD CLD_EXITED pid=28276 status=0x0000
write(1, " 0 1 2 3 4 5 6 7 8 9 A B".., 64) Err#4 EINTR
lwp_sigmask(SIG_SETMASK, 0x00020000, 0x00000000) = 0xFFBFFEFF [0x0000FFFF]
waitid(P_ALL, 0, 0x08046500, WEXITED|WTRAPPED|WSTOPPED|WNOHANG) = 0
waitid(P_ALL, 0, 0x08046500, WEXITED|WTRAPPED|WSTOPPED|WNOHANG) Err#10 ECHILD
sigaction(SIGCLD, 0x08046510, 0x08046580) = 0
setcontext(0x08046430)
write(1, 0x080F0FD8, 64) (sleeping...)
write(1, " 0 1 2 3 4 5 6 7 8 9 A B".., 64) = 64
ioctl(0, I_PEEK, 0x08046694) = 0
答え1
これはのバグのように見えますksh
。
私が疑うのは
x=$(file /tmp)
ksh
コマンドを実行し、パイプを介して出力を読み取る新しいプロセスを作成してfile
終了するのを待ちません(kshの最新バージョンを含むすべての最新のシェルはこれを行います)。コマンドの読み込み中にEOFに達すると、そこから返されます。パイプ。
この動作は、以下を実行することで確認できます。
ksh -c 'x=$(exec sh -c "echo foo;exec >&-; sleep 10"); echo "$x"'
そして、ksh
出力があることを確認してすぐに返すか、foo
10秒後に返します。
これは、file
コマンドが終了し、SIGCLDが対応する親(シェル)に送信されることを意味します。後ろにコマンドがx=...
返されました。
シェルの目的は、子プロセスの終了を要求する SIGCLD を処理することです。シェルにバックグラウンドで実行されているサブプロセスがある場合は、いつでも終了する準備ができている必要があります。 SIGCLD信号は、無視できない他の信号と同様にシステムコールをブロックします。中断。これを行うには、シェルは次のいずれかで準備する必要があります。ブロックする潜在的に中断されたシステムコールの実行中に信号をエクスポートするか、信号を処理した後に中断されたシステムコールを再試行します。
この場合、何も起こっていないようです。ほとんどの場合、write
組み込み関数を実行するkshが実行するシステムコールはecho
すぐに返されるため、中断する機会はありませんが、stdoutが指すパイプがいっぱいになるとシステムコールはブロックされますwrite
。 SIGCLDによって中断されました。 ksh は再試行しません。それはエラーです。
Linuxでも以下を実行すると、同じ動作を見ることができます。
strace -e write ksh -c 'i=0; while [ "$i" -lt 2000 ]; do : &
echo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
i=$(($i+1)); done' | (sleep 3; wc)
それから私達は次を見ます:
write(1, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"..., 61) = ? ERESTARTSYS (To be restarted)
--- SIGCHLD (Child exited) @ 0 (0) ---
write(1, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"..., 61...
同様に:
、コマンドを終了するとブロックwrite
システムコールは中断されますが、今回はwrite
再試行されます。
回避策には、組み込みコマンドを呼び出す前にコマンドのオーバーライドを回避したり、サブシェルでコマンドを実行するなど、SIGCLDを取得したプロセス以外のプロセスによって置き換えが行われるようにすることがecho
含まれます。write
echo
(echo "012...")
編集する:出力を詳しく見てみると、truss
2番目のループのトレースであることがわかります。これは、他のループを実行するプロセスとは別のプロセスで実行することを意図しているため、コマンドの終了時にSIGCLDを取得しないでくださいfile
。ただし、最初のループを実行するサブシェルが終了すると、SIGCLDを取得できます。
また、テスト結果が示すように、kshが実際にコマンド置換のために生成されたプロセスを待っている場合、受信したSIGCLD信号はコマンドの非同期シャットダウンとして説明することはできませんfile
。
外部パイプがいっぱいになる可能性が高いように見えますが、2つのwhileループ間のパイプはそうではありません。 SIGCLDは、echo
2番目のループのブロック期間中に受信され、1番目のループが終了したときに発生します。したがって、より効率的な解決策は、サブシェルecho
で各コマンドを実行するのではなく、サブシェルで2番目のループを実行することです。
while ...; done | (while ...;done)