シェルで何度も素早く連続して実行しても、時には動作し、時には動作しないコマンドが偶然に見つかりました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.txt
awk
出力が書き込まれる前にコマンドブロックをawk
実行するBEGIN
とエラーが発生します。wc
n.txt
。この場合、n
変数は空であるため、数値として使用するとゼロになります。BEGIN
ファイルを作成して実行すると、すべてがうまく機能します。
これが起こる場合は、オペレーティングシステムスケジューラとどのプロセスが最初にスロットをインポートするかによって異なります。最終バージョンがawk
早く実行されるか、wc
パイプラインが少し遅れてスケジュールされると、ジョブのawk
実行を開始するとファイルはまだ空になり、すべてが中断されます。どのコアが競合ポイントに到達するかによって、これらのプロセスが実際には他のコアで同時に実行される可能性が高くなります。あなたが得る効果は、コマンドが頻繁に機能しますが、時々あなたが投稿したエラーのために失敗することです。
通常、パイプはパイプの場合にのみ安全です。 stdoutからstdinまでは大丈夫ですが、プロセスは並列に実行されるため注文のために他の通信チャネルに依存することは信頼できません。、ファイルなどのプロセス、またはあるプロセスの一部は、標準入力を読み取って一緒にロックしない限り、他のプロセスの一部の前または後に実行されます。
ここで解決策は、必要になる前にすべてのファイルの書き込みを完了することです。行末で次のコマンドを実行する前に、パイプ全体とすべてのリダイレクトが完了していることを確認してください。このコマンドは決して信頼できませんが、この構造で実際に動作する必要がある場合は、最後のコマンドを実行する前sleep
に空にならないまで遅延()またはループを挿入して、必要な方法で動作する可能性を高めることができます。n.txt
awk
答え2
pipe
の式はprocess substitution
競争条件を引き起こしますbash
がksh
、zsh
そうではありません。
ここで最も重要な問題はzsh
待つのではbash
なく、待つことです。
詳細を見ることができますここ。
クイックフィックスを追加してsleep 1
常に利用できるawk
ようにします。n.txt
awk 'BEGIN{system("sleep 1");getline n < "n.txt"};{print $1 "\t" $1/n*100 "\t" $2}'
答え3
競合状態が設定されました。ただし、wc
レコードを個別に計算する必要がないより簡単な解決策が必要な場合は、次のようにawk
します。
awk '{if($2!~/\*/){print $1;++n}END{print n >"n.txt"}' tmp | sort | uniq -c ...
それ以外にも、値がメモリに合う限りawk
計算でき、x / n計算も実行できますが、sort|uniq -c
match / actionを使用して「ランダム」の順序で出力するのもよりきれいです。
awk '$2!~/\*/{++k[$1];++n} END{for(i in k){print k[i]"\t"k[i]/n*100"\t"i}}' tmp | sort -k1nr
または最近牛に似た一種の栄養 awk
正しい順序を使用して無効にすることがPROCINFO["sorted_in"]="@ind_num_desc"
できます。for
sort