きたへふ(Cチーム)のブログ

ファミスタとは特に関係ありません。タブレット・スマホをご利用の方は、できればPCモードで御覧ください。

Solarisの/bin/shの変数の挙動について(サブシェルの問題)

2005年12月15日 | サーバ・プログラム
私はお仕事でSolarisサーバの管理をするときにBourneシェルのスクリプトを
よく作成するのですが、while (do~done)のブロック中に操作した変数の値が
変更されていない事象が発生して、頭を抱えていました。
その件についての議論をコピペします。

UNIX板 シェルスクリプト総合 その4 より
http://pc8.2ch.net/test/read.cgi/unix/1131026501/


【2011.6.18 追記】
 gooブログの仕様で、半角の<で始まる英数字が連続すると(エスケープ
させても)文字化けする個所があるので、一部全角文字に置き換えています。




554 名無しさん@お腹いっぱい。 sage 2005/12/10(土) 17:46:47
Solaris8の/bin/shの変数の挙動について教えてください。

COUNTという変数があって

----
COUNT=0 ・・・0で初期化
while ほげ
do
   :
  (whileループ中にカウントアップ処理)
  echo "COUNT=$COUNT" ・・・・(1)
   :
done
echo "ループ終了後のCOUNT=$COUNT" ・・・・(2)
----

という処理を実行させたところ、whileループ中の(1)の処理では
正常にカウントアップされた値が表示されるのですが、
ループ終了直後の(2)の値は、何度実行させても0になります。(;´Д`)

こうなる原因はなんでしょうか?また改善方法についてヒントをください。
おながいします。



555 名無しさん@お腹いっぱい。 sage 2005/12/10(土) 18:01:22
>>554
サブシェルで実行されてるから



556 名無しさん@お腹いっぱい。 sage 2005/12/10(土) 18:08:24
サブシェルの問題だと思うが、そのコードだと説明不十分
たとえば下のコードならちゃんと 10 と出る。(Sol8)

#!/bin/sh
count=0
while [ $count -lt 10 ]
do
 count=`expr $count + 1`
 echo "count=$count"
done
echo "last:$count"

サブシェルの問題はUNIX FAQに載ってる
http://www.nurs.or.jp/~asada/FAQ/UNIX/section3.8.html



557 554 sage 2005/12/10(土) 23:17:02
>>555-556
ありがとうございますた。

とりあえず下記のサンプルで試してみました

パターン1
----------------
#!/bin/sh
COUNT=0
while read i
do
    COUNT=`expr $COUNT + 1`
    echo "now count = $COUNT"
done </etc/passwd
echo "Last count = $COUNT"
----------------


パターン2
----------------
#!/bin/sh
COUNT=0
cat /etc/passwd | while read i
do
    COUNT=`expr $COUNT + 1`
    echo "now count = $COUNT"
done
echo "Last count = $COUNT"
----------------



558 554 sage 2005/12/10(土) 23:17:33
パターン3
----------------
#!/bin/sh
COUNT=0
exec 9<&0 < /etc/passwd
while read i
do
    COUNT=`expr $COUNT + 1`
    echo "now count = $COUNT"
done
exec 0<&9 9<&-
echo "Last count = $COUNT"
----------------

そうしたら、パターン1とパターン2のLast countは0のままで、
パターン3は正確な値がでました。
ちなみにパターン1のシェル指定を/bin/bashに変えたらあっさりと
正確な値が出たので、とりあえずはbashで逃げることにします。(;´Д`)



559 名無しさん@お腹いっぱい。 sage 2005/12/10(土) 23:33:09
>>558
bashismですね。



560 名無しさん@お腹いっぱい。 sage 2005/12/11(日) 01:13:02
bourne シェルの有名な欠点だな。

kshにすれば、パターン1もパターン2も値を引き継げる。

ちなみに、bashは変態仕様なんで、
パターン1は値を引き継げるけど、パターン2はダメ。



563 名無しさん@お腹いっぱい。 sage 2005/12/11(日) 03:40:29
>>560
bash はパイプライン中に変数を定義しても
ダメみたいだな。

$ bash
$ a=1
$ echo $a
1
$ ls | a=2
$ echo $a
1



561 sage 2005/12/11(日) 01:56:05
うん、Solaris決め打ちでいいならkshにすべきだよね。



562 名無しさん@お腹いっぱい。 sage 2005/12/11(日) 03:10:01
zsh も、ksh と同様パターン1~パターン3全て動くんだよね。

ksh だと対話的に使った場合の機能がちょっと足らんという場合、
bash を使う人が多いんだけど、zsh の方が幸せだと思う。

スクリプトで使う場合には、ksh の方が小さいからいいだろうね。



564 名無しさん@お腹いっぱい。 sage 2005/12/11(日) 10:11:03
kshって昔からSolaris(というかSVR4)に入っているのに
あんまり使われてないね。
メニュー表示(select)とかシェルスクリプトに向いてる機能があるのに



565 554 sage 2005/12/11(日) 10:43:33
コメントありがとうございます。
bashにもそういうクセがあるとは知らなかったし、Solaris標準のシェルであれば
何でもいいので、kshかzshを使ってみようと思います。



566 名無しさん@お腹いっぱい。 sage 2005/12/11(日) 10:59:04
zsh は Solaris 標準じゃねーぞ。
今は勝手に入ってることが多いのはたしかだけど、
インストールのしかたによっては zsh は入らないこともあったはず。



上記の話を総合すると、下記のようなプログラム構成の場合

--------------------
1行目 変数a=0 で初期化
2行目 while (条件式)
3行目 do
4行目  変数a=10と変更
5行目 done
6行目 変数aの値を表示
--------------------

通常のプログラムでは、1行目で宣言した変数aのスコープは全体に渡るので、
6行目を実行させると(1回はwhileループを通るとして) a=10と表示されるのが
本来想定される動きである。

ところが Bourneシェルでは、whileのような制御構造をリダイレクトすると
(引用文のサンプル1のように、whileに対して </etc/passwd のように
ファイルをリダイレクトで読み込ませると)、その部分がサブシェル(≒関数)
として動くため、上記でいう4行目の変数aは、1行目や6行目の変数aとは
別物として扱われる動きになるらしい。
だから6行目の変数aの値が0として表示されるとのこと。

ちなみにbashは上記のようなファイルリダイレクトだと正確な値が出るものの、
引用文のサンプル2のように、whileに対して cat /etc/passwd | とパイプライン
経由でファイルの中身を渡してあげるパターンでは、NGになるようである。
(これがbashの変態仕様らしい)

ともかく、お勉強になりました。




UNIXシェルスクリプトコマンドブック 第2版
山下 哲典
ソフトバンククリエイティブ

このアイテムの詳細を見る

UNIXシェルスクリプトハンドブック (Technical handbook series (001))
関根 達夫
ソフトバンククリエイティブ

このアイテムの詳細を見る

『サーバー』 ジャンルのランキング
コメント   トラックバック (1)   この記事についてブログを書く
この記事をはてなブックマークに追加
« 小倉~博多間の新幹線に思う | トップ | ナイター観戦記(2006/6/1横浜... »

コメントを投稿


コメント利用規約に同意の上コメント投稿を行ってください。

数字4桁を入力し、投稿ボタンを押してください。

あわせて読む

トラックバック

この記事のトラックバック  Ping-URL
bashのバグ(仕様か?) (ネット探訪)
シェルでプログラムを書いていた。ああ、これはシェルのバグではないかと思つた話。