[bash] whileループ内で変数を変更してもループ外に反映されない
久々の投稿なので、書き方を忘れています。rickです。お久しぶりです。
唐突のネタですが、昨日(12/19(水))普通にハマって混乱した内容なので、覚え書きも兼ねて書いてみようと思います。
標準出力をwhileで受けて変数に格納する処理
test.txtの3行を順次読み込みながらW_SAMPLE
変数に文字列を格納している、ただそれだけの処理です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
$ cat test.txt AAA BBB CCC $ W_SAMPLE=XXX $ echo ${W_SAMPLE} XXX $ cat test.txt | while read line > do > W_SAMPLE=${line} > echo ${W_SAMPLE} > sleep 30 > done AAA BBB CCC $ echo ${W_SAMPLE} XXX |
私は、当然のように最後のCCC
がW_SAMPLE
に格納されているものと思っていましたが、結果は上記の通りです。
これは有名な話らしいのですが、私は知りませんでした。少しハマって悩んでしまいました。
上記の処理中のプロセスを別コンソールから確認してみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$ ps -efH | grep pts/2 | grep -v grep rick 5724 3014 0 01:39 pts/2 00:00:00 bash rick 7730 5724 0 01:56 pts/2 00:00:00 bash rick 7731 7730 0 01:56 pts/2 00:00:00 sleep 30 $ ps -efH | grep pts/2 | grep -v grep rick 5724 3014 0 01:39 pts/2 00:00:00 bash rick 7730 5724 0 01:56 pts/2 00:00:00 bash rick 7777 7730 0 01:56 pts/2 00:00:00 sleep 30 $ ps -efH | grep pts/2 | grep -v grep rick 5724 3014 0 01:39 pts/2 00:00:00 bash rick 7730 5724 0 01:56 pts/2 00:00:00 bash rick 7818 7730 0 01:57 pts/2 00:00:00 sleep 30 $ ps -efH | grep pts/2 | grep -v grep rick 5724 3014 0 01:39 pts/2 00:00:00 bash |
分かりにくいですが、プロセスID:5724 が上記の cat | while
を実行したbashのプロセスです。
その1つ下に表示されているプロセスID:7730 がパイプを通してforkされたbashのプロセスです。もう別物です。
そしてさらに1つ下に表示されているプロセスID:7731,7777,7818が whileループ内で実行されたsleepコマンドのプロセスです。 whileループの中で3回sleepは実行されていますので、その都度プロセスIDが変わっていますね。
一番最後のpsコマンド結果は、whileループを抜け出したあとなので、コンソール画面に戻ってきている感じですね。
このプロセスの構成だと、そりゃあW_SAMPLE
にCCC
は格納されませんよね。
回避方法
上記のような特性を理解した上でシェルスクリプトの実装を進めていけば問題にはならないと思いますが、UNIXのsh(Bourne Shell)では意図した結果になっていたので、移植時に混乱する可能性があります。
(私はHP-UX 11i v3(UNIX)環境下で普通に動作していたシェルスクリプトをRHEL 7.4(Linux)上で動かそうとして捕まりました。可能性じゃなくて混乱しました)
回避策(非互換対応)を知っておいて損はなさそうなのでここに記しておきます。 簡単に言ってしまえば、パイプを使わなければよいのです(多分)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
$ W_SAMPLE=XXX $ cat test.txt AAA BBB CCC $ while read line > do > W_SAMPLE=${line} > echo ${W_SAMPLE} > sleep 30 > done < test.txt AAA BBB CCC $ echo ${W_SAMPLE} CCC |
1 2 3 4 5 6 7 8 9 10 11 |
$ ps -efH | grep pts/2 | grep -v grep rick 5724 3014 0 01:39 pts/2 00:00:00 bash rick 10346 5724 0 02:28 pts/2 00:00:00 sleep 30 $ ps -efH | grep pts/2 | grep -v grep rick 5724 3014 0 01:39 pts/2 00:00:00 bash rick 10469 5724 0 02:28 pts/2 00:00:00 sleep 30 $ ps -efH | grep pts/2 | grep -v grep rick 5724 3014 0 01:39 pts/2 00:00:00 bash rick 10510 5724 0 02:29 pts/2 00:00:00 sleep 30 $ ps -efH | grep pts/2 | grep -v grep rick 5724 3014 0 01:39 pts/2 00:00:00 bash |
今回は「標準入力」としてファイルを読み込むように変更しました。 UNIXコマンドは実現方法が多種多様で面白いです。
上記以外にも回避方法はあるかと思います(変数の値をファイルに退避しておく等)。 コーディング/テスト時には気付けると思う罠ですが、他の人が作成したシェルスクリプトを読んだり修正したりする場合は要注意です。
動作確認環境
OS: CentOS Linux release 7.5.1804
bash: GNU bash, バージョン 4.2.46(2)-release (x86_64-redhat-linux-gnu)