非結晶出力を持つパイプ

非結晶出力を持つパイプ

シェルで何度も素早く連続して実行しても、時には動作し、時には動作しないコマンドが偶然に見つかりましたbash(他のシェルでは動作をテストしませんでした)。問題は、BEGINパイプの末尾にあるステートメントブロックの変数を読み取ることに限定されました。awk一部の実行中は変数がBEGINブロックから正しく読み取られますが、他の実行中は操作が失敗します。この異常な動作が他の人によって再現できると仮定すると(私のシステムの問題による結果ではない)、その不一致を説明できますか?

次のファイルを入力として使用しますtmp

cat > tmp <<EOF
a   a
b   *
aa  a
aaa a
aa  a
a   a
c   *
aaa a
aaaa    a
d   *
aaa a
a   a
aaaaa   a
e   *
aaaa    a
aaa a
f   *
aa  a
a   a
g   *
EOF

私のシステムでは、パイプ

 awk '{if($2!~/\*/) print $1}' tmp | tee >(wc -l | awk '{print $1}' > n.txt) | sort | uniq -c | sort -k 1,1nr | awk 'BEGIN{getline n < "n.txt"}{print $1 "\t" $1/n*100 "\t" $2}'

正しい出力が生成されます。

4   28.5714 a
4   28.5714 aaa
3   21.4286 aa
2   14.2857 aaaa
1   7.14286 aaaaa

またはエラーメッセージ:

awk: cmd. line:1: (FILENAME=- FNR=1) fatal: division by zero attempted

どのようにコマンドを発行できますか?可能乱数生成が含まれず、その間に環境が変更されていない場合、連続して2回実行すると、別の出力が提供されますか?

この動作がどれほど素晴らしくないかを示すために、上記のパイプラインを10回連続して実行して生成された出力を見てみましょう。

for x in {1..10}; do echo "Iteration ${x}"; awk '{if($2!~/\*/) print $1}' tmp | tee >(wc -l | awk '{print $1}' > n.txt) | sort | uniq -c | sort -k 1,1nr | awk 'BEGIN{getline n < "n.txt"}{print $1 "\t" $1/n*100 "\t" $2}'; done
Iteration 1
awk: cmd. line:1: (FILENAME=- FNR=1) fatal: division by zero attempted
Iteration 2
4   28.5714 a
4   28.5714 aaa
3   21.4286 aa
2   14.2857 aaaa
1   7.14286 aaaaa
Iteration 3
4   28.5714 a
4   28.5714 aaa
3   21.4286 aa
2   14.2857 aaaa
1   7.14286 aaaaa
Iteration 4
awk: cmd. line:1: (FILENAME=- FNR=1) fatal: division by zero attempted
Iteration 5
awk: cmd. line:1: (FILENAME=- FNR=1) fatal: division by zero attempted
Iteration 6
awk: cmd. line:1: (FILENAME=- FNR=1) fatal: division by zero attempted
Iteration 7
4   28.5714 a
4   28.5714 aaa
3   21.4286 aa
2   14.2857 aaaa
1   7.14286 aaaaa
Iteration 8
awk: cmd. line:1: (FILENAME=- FNR=1) fatal: division by zero attempted
Iteration 9
4   28.5714 a
4   28.5714 aaa
3   21.4286 aa
2   14.2857 aaaa
1   7.14286 aaaaa
Iteration 10
awk: cmd. line:1: (FILENAME=- FNR=1) fatal: division by zero attempted

close注:ファイルが開いていることに関する問題である場合に備えて、変数を読み取った後にファイル(awk)を閉じようとしました。ただし、一貫性のない出力がまだ存在します。

ベストアンサー1

リダイレクトに競合条件があります。これ:

>(wc -l | awk '{print $1}' > n.txt)

並列実行:

awk 'BEGIN{getline n < "n.txt"}...'

後でパイプラインで。時には、プログラムが実行を開始すると、n.txtまだ空です。awk

これはBashリファレンスマニュアルに(間接的に)文書化されています。中管路:

パイプラインの各コマンド出力は、次のコマンドの入力にパイプされます。つまり、各コマンドは前のコマンドの出力を読み込みます。この接続は、コマンドで指定されたリダイレクトの前に実行されます。

それから:

パイプラインの各コマンドは、独自のサブシェルで実行されます。

(強調追加)。みんなパイプのプロセスは、前のプログラムが完了するか、またはタスクの実行を開始するのを待たずに、入力と出力が一緒に接続された状態で開始されます。それの前に、プロセスの交換そして>(...)

パラメータと変数拡張、コマンド置換、算術拡張を同時に実行します。

これは、コマンドを実行するサブプロセスが早く開始され、その前にリダイレクトがクリアされますが、エラーを引き起こすwc -l | awk ...プロセスがまもなく開始されることを意味します。どちらのコマンドも並列に実行されます。同時に複数のプロセスが実行されます。n.txtawk

出力が書き込まれる前にコマンドブロックをawk実行するBEGINとエラーが発生します。wcn.txt。この場合、n変数は空であるため、数値として使用するとゼロになります。BEGINファイルを作成して実行すると、すべてがうまく機能します。

これが起こる場合は、オペレーティングシステムスケジューラとどのプロセスが最初にスロットをインポートするかによって異なります。最終バージョンがawk早く実行されるか、wcパイプラインが少し遅れてスケジュールされると、ジョブのawk実行を開始するとファイルはまだ空になり、すべてが中断されます。どのコアが競合ポイントに到達するかによって、これらのプロセスが実際には他のコアで同時に実行される可能性が高くなります。あなたが得る効果は、コマンドが頻繁に機能しますが、時々あなたが投稿したエラーのために失敗することです。


通常、パイプはパイプの場合にのみ安全です。 stdoutからstdinまでは大丈夫ですが、プロセスは並列に実行されるため注文のために他の通信チャネルに依存することは信頼できません。、ファイルなどのプロセス、またはあるプロセスの一部は、標準入力を読み取って一緒にロックしない限り、他のプロセスの一部の前または後に実行されます。

ここで解決策は、必要になる前にすべてのファイルの書き込みを完了することです。行末で次のコマンドを実行する前に、パイプ全体とすべてのリダイレクトが完了していることを確認してください。このコマンドは決して信頼できませんが、この構造で実際に動作する必要がある場合は、最後のコマンドを実行する前sleepに空にならないまで遅延()またはループを挿入して、必要な方法で動作する可能性を高めることができます。n.txtawk

おすすめ記事