SSDドライブを入手したときにUbuntu 12.10を新規インストールした後、最近 http://rvm.io の指示に従って)RVMを再インストールしました。
さて、私が入力したとき:type rvm | head -1
次のようなエラーが表示されます。
rvm is a function
-bash: type: write error: Broken pipe
しかし、私がすぐにコマンドを繰り返すならば、私は受け取るだけです:
rvm is a function
そしてそれはすべて大丈夫のようですか?何が起こっていますか?修正するにはどうすればよいですか。いつも起こるわけではありません。それはもっと散発的であるようです。私はそれに何らかの種類のパターンを見つけようとしましたが、まだしていません。
このような状況で「破損したパイプ」が表示されることはまれですが、通常のことです。
type rvm | head -1
を実行すると、bashはあるプロセスでtype rvm
を実行し、別のプロセスでhead -1
を実行します。1 type
の標準出力はパイプの「書き込み」端に接続され、head
の標準入力は「読み取り」端に接続されます。両方のプロセスが同時に実行されます。
head -1
プロセスはstdinからデータを(通常は8kBの塊で)読み込み、(-1
オプションに従って)1行を出力して終了し、パイプの「読み込み」の終わりを閉じます。 rvm
関数は非常に長いので(bashによって解析され再構築された後で約11kB)、これはhead
がまだ書き出すべき数kBのデータを持っている間にtype
が終了することを意味します。
この時点で、type
はもう一方の端が閉じられているパイプに書き込もうとしているので - 壊れたパイプ - それが呼び出したwrite()関数は"Broken pipe"として翻訳されたEPIPEエラーを返します。このエラーに加えて、カーネルはSIGPIPEシグナルをtype
にも送信します。これはデフォルトでプロセスを即座に強制終了します。
(このシグナルは対話型シェルでは非常に役に立ちます。ほとんどのユーザーは最初のプロセスを実行してどこにも書き込もうとしないためです。一方、非対話型サービスはSIGPIPEを無視します。そのような単純なエラーで死ぬ - それで彼らはエラーコードを非常に役に立つと思う。)
ただし、シグナルの配信は100%即時ではないため、write()がEPIPEを返し、シグナルを受信する前にプロセスが短時間実行され続ける場合があります。この場合、type
は失敗した書き込みに気付き、エラーコードを翻訳し、SIGPIPEによって強制終了される前にエラーメッセージをstderrに出力するのに十分な時間を得ます。 (type
はbash自体の組み込みコマンドなので、エラーメッセージには "-bash:type:"と書かれています。)
type
プロセスとカーネルのシグナルデリバリーコードは文字通り同時に異なるコア上で実行できるため、これはマルチCPUシステムではより一般的なようです。
Write()関数からEPIPEを受け取るとすぐに終了するように(bashのソースコード内の)type
ビルトインにパッチを当ててこのメッセージを削除することは可能です。
しかし、気にする必要はなく、rvm
のインストールとはまったく関係ありません。
このように、パイプにtail -n +1
を挿入することで、壊れたパイプを別のプロセスを犠牲にして修正できます。
タイプrvm | tail -n + 1 |頭-1
+1
は、入力の最初の行とそれに続くすべての行を出力するようにtail
に指示します。出力はtail -n +1
が存在しない場合とまったく同じになりますが、プログラムは標準出力をチェックするのに十分スマートで、パイプをきれいに閉じます。これ以上壊れたパイプはありません。
write error: Broken pipe
メッセージは、そのパイプの読み込み側にリーダーが残っていない状態でパイプに書き込もうとする書き込みプロセス、およびSIGPIPE
シグナルが現在のプロセスまたは親プロセスによって無視されるように設定されているという特別な状況を示します。 SIGPIPE
を無視するように設定したのが親プロセスだった場合、子プロセスが非対話型シェルでそれを元に戻すことはできません。
ただし、明示的なサブシェルを使用してtype rvm
が終了したときにhead -1
を強制終了することは可能です。このようにしてtype rvm
をバックグラウンド化し、typepid
をhead -1
サブシェルに送り、そこでEXIT
にトラップを実装してtype rvm
を明示的に殺すことができます。
trap "" PIPE # parent process sets SIGPIPE to be ignored
bash # start child process
export LANG=C
# create a fake rvm function
eval "
rvm() {
$(printf 'echo line of rvm code %s\n' {1..10000})
}
"
# rvm is a function
# bash: type: write error: Broken pipe
type rvm | head -1
# kill type rvm when head -1 terminates
# sleep 0: do nothing but with external command
( (sleep 0; type rvm) & echo ${!} ; wait ${!} ) |
(trap 'trap - EXIT; kill "$typepid"; exit' EXIT; typepid="$(head -1)"; head -1)