なぜ競合状態があるのか
パイプの2つの側面は並列ではなく、順番に実行されます。これを示す非常に簡単な方法があります。
time sleep 1 | sleep 1
これには2秒ではなく1秒かかります。
シェルは2つの子プロセスを開始し、両方が完了するのを待ちます。これら2つのプロセスは並行して実行されます。一方が他方と同期する唯一の理由は、もう一方を待つ必要がある場合です。同期の最も一般的なポイントは、右側が標準入力でデータが読み取られるのを待ってブロックし、左側がさらにデータを書き込むとブロックが解除されるときです。逆も発生する可能性があります。右側がデータの読み取りに時間がかかり、左側が書き込み操作をブロックして、右側がさらにデータを読み取るまでです(パイプ自体にバッファがあり、カーネルですが、最大サイズが小さくなっています)。
同期のポイントを観察するには、次のコマンドを観察します(sh -x
各コマンドを実行するたびに出力します)。
time sh -x -c '{ sleep 1; echo a; } | { cat; }'
time sh -x -c '{ echo a; sleep 1; } | { cat; }'
time sh -x -c '{ echo a; sleep 1; } | { sleep 1; cat; }'
time sh -x -c '{ sleep 2; echo a; } | { cat; sleep 1; }'
観察した内容に慣れるまで、さまざまなバリエーションを試してください。
複合コマンドの場合
cat tmp | head -1 > tmp
左側のプロセスは次のことを行います(説明に関連する手順のみを記載しています)。
cat
引数を指定して外部プログラムを実行しますtmp
。
tmp
読書のために開きます。
- ファイルの最後に達していない間に、ファイルからチャンクを読み取り、標準出力に書き込みます。
右側のプロセスは次のことを行います。
- 標準出力を
tmp
にリダイレクトし、ファイルを途中で切り捨てます。
head
引数を指定して外部プログラムを実行します-1
。
- 標準入力から1行読み取り、標準出力に書き込みます。
同期の唯一のポイントは、right-3がleft-3が1行を処理するのを待つことです。左2と右1の間には同期がないため、どちらの順序でも発生する可能性があります。それらが発生する順序は予測できません。それは、CPUアーキテクチャ、シェル、カーネル、プロセスがスケジュールされているコア、その時間の周りにCPUが受け取る割り込みなどに依存します。
動作を変更する方法
システム設定を変更して動作を変更することはできません。コンピュータはあなたがそれをするように言うことをします。切り捨てて並列にtmp
読み取るように指示したtmp
ので、2つの処理が並列に行われます。
OK、変更できる「システム設定」が1つあります/bin/bash
。bashではない別のプログラムに置き換えることができます。これは良い考えではないことは言うまでもありません。
パイプの左側の前で切り捨てを発生させたい場合は、パイプラインの外側に配置する必要があります。次に例を示します。
{ cat tmp | head -1; } >tmp
または
( exec >tmp; cat tmp | head -1 )
なぜあなたがこれを欲しがるのか私には分かりません。空であることがわかっているファイルから読み取る意味は何ですか?
逆に、cat
読み取りの終了後に出力のリダイレクト(切り捨てを含む)を実行したい場合は、メモリにデータを完全にバッファリングする必要があります。
line=$(cat tmp | head -1)
printf %s "$line" >tmp
または、別のファイルに書き込んでから、所定の場所に移動します。これは通常、スクリプトで物事を行うための堅牢な方法であり、元の名前で表示される前にファイルが完全に書き込まれるという利点があります。
cat tmp | head -1 >new && mv new tmp
moreutilsのコレクションはちょうどと呼ばれる、というんプログラムを含んでいますsponge
。
cat tmp | head -1 | sponge tmp
問題を自動的に検出する方法
不適切に記述されたスクリプトを取り、どこが壊れているかを自動的に把握することが目的である場合、残念ながら、人生はそれほど単純ではありません。cat
切り捨てが発生する前に読み取りが終了することがあるので、ランタイム分析では問題を確実に検出できません。静的分析は原則としてそれを行うことができます。質問の簡略化された例はShellcheckによって捕捉されますが、より複雑なスクリプトでは同様の問題を捕捉しない可能性があります。