同じパイプで同じファイルを読み書きすることを常に「失敗」させる方法は何ですか?

同じパイプで同じファイルを読み書きすることを常に「失敗」させる方法は何ですか?

次のスクリプトがあるとしましょう。

#!/bin/bash
for i in $(seq 1000)
do
    cp /etc/passwd tmp
    cat tmp | head -1 | head -1 | head -1 > tmp  #this is the key line
    cat tmp
done

重要な行で同じファイルを読み書きすることtmpは時々失敗します。

(これは競合状態のためであることを読んでいます。パイプラインのプロセスが並列に実行されるため、なぜなのか理解できませんhead。可能です。)

スクリプトを実行すると約200行が出力されます。このスクリプトが常にゼロ行を出力するように強制する方法はありますかtmp? (したがって、I / Oリダイレクトは常に最初に準備され、データは常に削除されます。)明らかに、これはスクリプトではなくシステム設定を変更することを意味します。

あなたの考えに感謝します。

ベストアンサー1

競争条件が存在する理由

パイプラインの両方が順番に実行されるのではなく、並列に実行されます。これを示す非常に簡単な方法があります。

time sleep 1 | sleep 1

これには2秒ではなく1秒かかります。

シェルは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

左側のプロセスは次のことを行います(説明に関連するステップのみをリストしました)。

  1. catパラメータを使用して外部プログラムを実行しますtmp
  2. 読書用に開いていますtmp
  3. ファイルの終わりに達していない場合は、ファイルからブロックを読み取り、標準出力に書き込みます。

右側のプロセスは次のことを行います。

  1. 標準出力にリダイレクトtmpし、プロセスからファイルを切り捨てます。
  2. headパラメータを使用して外部プログラムを実行します-1
  3. 標準入力から1行を読み、それを標準出力に書き込みます。

唯一の同期点は、left-3がフルライン処理を完了するのを待つright-3です。 left-2 と right-1 の間に同期がないため、どの順序でも発生する可能性があります。発生順序は予測できません。 CPU アーキテクチャ、シェル、カーネル、プロセスが予約されるコア、その時点で CPU が受け取る割り込みなどによって異なります。

行動を変える方法

システム設定を変更して動作を変更することはできません。コンピュータはユーザーの指示に従います。トリミングしてtmp並列に読み取るように指示するtmpと、2つの操作が並列に実行されます。

まあ、変更できる「システム設定」があります。/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

これその他のユーティリティコレクションには、これを行うように特別に設計されたというプログラムが含まれていますsponge

cat tmp | head -1 | sponge tmp

問題を自動的に検出する方法

あなたの目標が間違って書かれたスクリプトをインポートし、自動的に何が間違っているのかを見つけることであれば申し訳ありません。人生はそれほど簡単ではありません。場合によってはcat、切り捨てが発生する前に読み取りが完了するため、ランタイム分析では問題を確実に見つけることができません。静的分析は、原則として質問キャプチャの単純化された例を介して実行できます。住宅検査しかし、より複雑なスクリプトでは、同様の問題を捉えることができない可能性があります。

おすすめ記事