bash:変数は、while読み取りループの終わりに値を失います


36

シェルスクリプトの1つに問題があります。数人の同僚に尋ねましたが、彼らは皆、頭を振っただけです(少しひっかいた後)ので、私は答えを求めてここに来ました。

私の理解によると、次のシェルスクリプトは最後の行として「Count is 5」を出力するはずです。そうでないことを除いて。「Count is 0」と出力されます。「while read」が他の種類のループに置き換えられた場合、正常に機能します。スクリプトは次のとおりです。

echo "1"> input.data
echo "2" >> input.data
echo "3" >> input.data
echo "4" >> input.data
echo "5" >> input.data

CNT = 0 

cat input.data | 読みながら
行う
  CNT ++;
  echo "$ CNTへのカウント"
やった 
echo "カウントは$ CNT"

なぜこれが起こり、どうすればそれを防ぐことができますか?Debian LennyとSqueezeでこれを試しましたが、同じ結果です(つまり、bash 3.2.39とbash 4.1.5。シェルスクリプトウィザードではないことを完全に認めているので、ポインタをいただければ幸いです。

回答:


30

引数@ Bash FAQエントリ#24を参照してください:「変数をループに設定します。ループが終了した後、変数が突然消えるのはなぜですか?または、データをパイプで読み取れないのはなぜですか?」(最近アーカイブされ)。

概要:これは、bash 4.2以降でのみサポートされています。bashを使用している場合、パイプの代わりにコマンド置換などのさまざまな方法を使用する必要があります。


あなたの答えが私に幅広い選択肢を提供してくれたので、あなたはボーナスを受け取ります。
wolfgangsz

5
リンクは無効です。これが、リンクのみの回答が悪い理由です。少なくともここで答えを要約してください。
-rudolfbyker

神、まだkshの方がはるかに優れている...なぜ、なぜ誰もがbashに群がったのか。
フロリアンハイグル

@FlorianHeigl:kshはOne True Shellだと主張していますか?
イグナシオバスケス-エイブラムス

@ IgnacioVazquez-Abramsいいえ、しかし、bashでのwhileループの処理は恐ろしいPITAであると主張しています。ループ処理は、1993年の機能に追いつかないようにするロングランナーでした。他のものはgetopt処理で、(1993年も)組み込みのハンドラーはシンプルで機能していました。つまり、docoptを使用しない限り取得できないものです。私は、bashが20年以上にわたって常に遅れを取っていると主張しています。ここでこれまたは何百万ものgetoptsの使用に費やされた時間は計り知れません-ほとんどの人は決して知らないので受け入れられます。
フロリアンハイグル

30

これは一種の「よくある」間違いです。パイプはSubShellを作成するため、while readスクリプトとは異なるシェルで実行されているため、CNT変数は変更されません(パイプサブシェル内のシェルのみ)。

最後echoのサブシェルwhileをグループ化して修正します(修正する方法は他にもたくさんありますが、これは1つです。イアンとイグナシオの答えは他にもあります)。

CNT=0

 cat input.data | ( while read 
do
  let CNT++;
  echo "Counting to $CNT"
done 
echo "Count is $CNT" )

長い説明:

  1. CNTスクリプトで値0を宣言します。
  2. サブシェルはtoで開始さ|while readます。
  3. あなたの$CNT変数が値0でサブシェルにエクスポートされます。
  4. SubShellはカウントし、CNT値を5に増やします。
  5. SubShellが終了し、変数と値が破棄されます(呼び出しプロセス/スクリプトに戻りません)。
  6. あなたはechoあなたの元CNT0の値。

2
私が書いた最初のシェルスクリプトは同じ問題を私に与え、パイプが追加のシェルを生成することを知る前にしばらく壁に頭を打ちました。パイプ内で混乱した変数は、パイプが終了するとすぐにスコープから外れます。つまり、実際にそれが使用されたパイプの外部の変数を使用して何かを実行したい場合は、一時ファイルのようなファンキーなもので状態を保持します。
photoionized

素晴らしい答えです。残念ながら、私は受け入れボーナスを1つしか与えることができません。ごめんなさい。
wolfgangsz

10

これは動作します

CNT=0 

while read ;
do
  let CNT++;
  echo "Counting to $CNT"
done <input.data
echo "Count is $CNT"

必要なデータがどこにあり、それを取り戻す必要があるだけであるため、スマートな方法が気に入っています。高度なスキルのソリューションを知らない場合は、いつでも「ファイルを読む」ことができます。あなたのために+1。
m3nda

1
これを読んでいる人は誰でも、最初の行が#!/ bin / bashで、#!/ bin / shが機能しないため、スクリプトが明示的にbashを呼び出す場合にのみIainが提供するソリューションが機能することに注意してください。
-Roadowl

1
面白い、猫の無用な使用が実際にコードの動作を妨げた場所を見た最初の例。道@Roadowlことで、ここでの唯一のbashismはラインであるlet CNT++代わりにする必要がありますCNT="$((CNT+1))"を使用するためにPOSIX準拠の算術展開を。残りはすでに移植可能です。
ワイルドカード

6

whileループの前のファイルのように、代わりにサブシェルでデータを渡してみてください。これはlainのソリューションに似ていますが、断続的なファイルが必要ないことを前提としています。

total=0
while read var
do
  echo "variable: $var"
  ((total+=var))
done < <(echo 45) #output from a command, script, or function
echo "total: $total"
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.