しらいです。 In Message-Id <200211080816.AA00040 _at_ dde086n.cp.star-micronics.co.jp> Kenichi Nagai <nagai _at_ cp.star-micronics.co.jp>さんwrites: > 永井@Linux初心者です。 > bashのwhileループについて教えてください。 > cat等のコマンド結果をwhileループに渡して処理した場合、whileループ中 > でカウントアップされたシェル変数値が、ループを抜けた後にうまく反映さ > れません。 それは while のせいではなくパイプ (|) のせいです。「|」で 多段に接続された各々のコマンドは別プロセスとして動きますので、 そのいずれのプロセスも親である shell に変数を渡すことは出来 ません。 MS-DOS のように中間ファイルによって実現されている疑似パイ プと異なり、UNIX の pipe(2) によって実現されているパイプの場 合、前段の実行が完了しなくても後段の実行が始まります。 これは個々のプロセスが親である shell とは独立して動いてい ることを意味しますので、その while は sub shell と呼ばれる子 プロセスによって実行されている訳です。 shell 変数は一つのプロセス内でしか共有出来ませんから、その 内容を他のプロセスから見ることは不可能です。ファイルに吐き出 すか、もしくはパイプ以外の手段を講じるかですね。 bash の場合、Bourne shell と異なりこの辺りの些末な仕様が異 なります。 例えば、 パイプの最後段が単純な外部コマンドだった場合: Bourne shell -> sub shell の子 bash -> shell 自身の子 while が redirect されていた場合: Bourne shell -> while は sub shell bash -> while は shell 自身 といったような仕様になっています。 bash の場合、極力 sub shell を作らないように心掛けているん でしょうね。この特徴を使うと、while に渡す入力を cat からの パイプではなく redirectee 「<」を使って渡せば sub shell 化し なくて済むことになります。 cnt=0 while read LINE do echo "$cnt: $LINE" cnt=`expr $cnt + 1` done < $0 echo "cnt(after LOOP) = $cnt" 0: cnt=0 1: while read LINE 2: do 3: echo "$cnt: $LINE" 4: cnt=`expr $cnt + 1` 5: done < $0 6: echo "cnt(after LOOP) = $cnt" cnt(after LOOP) = 7 但し、bash の特徴を使っているために、この手法は他の Bourne shell 互換 shell では使えません。bash は Bourne shell 系の眷 族の中でもかなり異端な部類に属しますので、そういう shell に 依存した実装は良くないかも知れません。 となると、shell 変数以外の入出力を使ってプロセス間でのデー タ授受を行なうことが必要です。簡単なのは標準入出力を使う実装 でしょう。 while_loop=' while read LINE || { echo $cnt && false; }; do echo 1>&3 3>&- "$cnt: $LINE"; cnt=`expr $cnt + 1`; done ' cnt=0 exec 3>&1 cnt=`cat $0 | eval $while_loop` echo "cnt(after LOOP) = $cnt" 0: while_loop=' 1: while read LINE || { echo $cnt && false; }; 2: do 3: echo 1>&3 3>&- "$cnt: $LINE"; 4: cnt=`expr $cnt + 1`; 5: done 6: ' 7: cnt=0 8: exec 3>&1 9: cnt=`cat $0 | eval $while_loop` 10: echo "cnt(after LOOP) = $cnt" ここでは幾つかの TIPS を使っています。主だったところを紹介 しましょう。 1. 標準出力の複製 while ステートメントの標準出力を `` で拾って変数値を得よう としていますので、while の中から標準出力に吐き出すと、その文 字列まで一緒に変数値として取込まれてしまいます。 そうならないために、`` の実行前に予め標準出力の複製を作っ ておきます。ここでは file descriptor #3 に標準出力 #1 をコピ ーしています。(line 8.) こうしておけば、while の中でもこの #3 に対して出力してやれ ば、元々の標準出力に対する出力になります。(line 3.) 2. while ステートメントのブロック化 残念なことに、この while ステートメントはその中で `` を使 ってしまっています。`` は入れ子にすることが出来ないので、ど こかでこの while ステートメントだけをブロック化しておく必要 があります。 そのための手段は幾つかありますが、ここではステートメント全 体を変数に代入し、後でそれを eval で評価する手法を採りました。 この手法が一番汎用性があります。(line 9.) 汎用性の低い方法としては、他に以下のようなものがあります。 ・`` の代わりに入れ子可能な $() を使う。 → bash 依存 (POSIX 規格にはある) ・while ステートメントを関数化する。 → Bourne shell 依存 (古い実装では関数が使用不可) 3. 改行位置に「;」を追加 while ステートメントを '' で括って文字列化してしまいました ので、改行が単なるフィールドセパレータとして扱われてしまいま す。空白やタブと同列の存在です。 コマンドリストのセパレータには改行以外に「;」が使えますの で、元々の改行位置に「;」を追加しました。(line 1.-4.) # 「do」の後ろには元々セパレータは要りません。 4. read 完了時に $cnt を出力させる while ステートメントの中で $cnt を出力させるには、ループの 中で毎回出力させる方法もありますが、これだと最終的な $cnt の 値を取得するのに面倒な手順を必要とします。 read が失敗した時点ですぐに while を抜けないように、その後 ろに選言条件で echo $cnt を付けました。これで、read に失敗し た時、即ちループ終了時に $cnt が出力されます。 但し、それだけだとループが終わらなくなってしまうので、echo の後に false で無理矢理失敗させています。(line 2.) # この {} によるグループ化は単純に条件演算の優先順位による #ものです。別に () で括っても構いませんが、shell 文法の () #はグループ化ではなくて sub shell 化を意味します。 > テスト用に、w_test_sh を作成し、実行した結果が、result.txtです。 > w_test_sh中の最初のブロックでは、while loopを回しています。 > ループの中でcnt変数をインクリメントして、ループを抜けた後にcnt変数を > 表示させるとちゃんと"5"になっています。 ステートメントが実行中の shell の中で閉じて実行される分に は、このように変数値も取得出来ます。どこで sub shell 化する かについてはマニュアルでも触れられていないので判断が難しいで しょうね。 私も自分で shell を実装するようになって、さんざん調べ廻っ た結果得た知識が多いです。やってみないと判らないことが多いの で、なかなか難しいでしょうね。 最近だと Bourne shell そのものの source が参照可能になって いるのですが、あの source は BASIC ライクに記述されているの で私には解析不能です。 そういった実装の揺れを解消すべく、POSIX 規格が整備されてき ているんだと思いますが、あの中では大まかな仕様しか記述されて いないので、こういった些末な挙動については自分で一つ一つ地道 に調べていくしかないんでしょうかね。 しらい たかし
Follow-Ups: References:
- Prev by Subject: [linux-users:96158] Re: [bash]whileループ内変数値の扱いについて教えてください。
- Next by Subject: [linux-users:96160] Re: [bash]whileループ内変数値の扱いについて教えてください。- 解決!
- Previous by thread: [linux-users:96158] Re: [bash]whileループ内変数値の扱いについて教えてください。
- Next by thread: [linux-users:96160] Re: [bash]whileループ内変数値の扱いについて教えてください。- 解決!
- Indexes:[Main][Thread]