2007/04/07

ksh v.s. bash - IFS変数の使いどころ & execコマンド

しばらく前にRHEL4を入れてあったPCにSATAディスクを2台付けてRAID0にしてみました。 /bootだけは起動のためにRAID1にしています。

OSを入れ直したタイミングでCentOSにしてしまいrpmforgeとaptを導入したけれど、 余ったRHEL4のライセンスはどうしようかな…。

もっとも最初からCentOSにしたかったわけではなく、理由はRed Hat(会社の方)が嫌になったから。 ubuntuにしようと思ったけれど、6.06のインストーラーではRAID0が構成できなかったために、しかたなくCentOS4にしたのでした。

ここから先はCentOS4での作業なので、bashは3.00、kshはpdksh 5.2.14が対象です。

さてRAID0なんて時限爆弾みたいなものだから、外付けのUSBディスクを使ってpdumpfsでバックアップをとることにしました。 USBは便利なんだけれど、PCを再起動するタイミングで一緒に電源のOn, OFFをしないと機嫌が悪くなる場合があるし、pdumpfsを実行するタイミングで確実にファイルシステムがマウントされている状態であってほしい。

考えたのは、"mount"コマンドの出力からマウントポイントの"/bk"とデバイスファイルの"/dev/mapper/bkvg-bklv"にマッチした行を見つけた場合にpdumpfsを実行する、次のようなスクリプト。

#!/bin/bash
function test_bk {
  flag=1
  mount | while read line
  do echo $line | grep /bk | grep "bkvg-bklv" >/dev/null
    test "$?" = "0" && flag=0 && echo flag is $flag ## for debug
  done
  echo return flag is $flag ## for debug
  return $flag
}
if test_bk ; then
  ## 以下 pdumpfs コマンドを実行

さて実行結果はあまり楽しくありません。出力結果のflagの値が食い違ってしまいます。 この値が"0"でないと、pdumpfsが呼ばれないのですが、常に1になってしまいます。

$ ./test_bk.sh
flag is 0
return flag is 1
..

いろいろサンプルプログラムを作ってみたところ、シェルのread組み込みコマンドがよくなかったらしい。 簡単な例ではforを使うとちゃんと動きます。

けれど、この場合は単純にwhileをforに書き変えただけではうまくいかないのでした。

#!/bin/bash
function test_bk {
  flag=1
  for line in $(mount)
  do echo $line | grep /bk | grep "bkvg-bklv" >/dev/null
$ ./test_bk.sh
flag is 0
return flag is 1

スクリプトを"bash -x script_name"の様に実行すると、"line"変数に行ではなく、空白で区切ったワードが順番にはいっていることがすぐにわかります。こういう動きをするところはIFS変数しかないかなと思ったので、さっそくもう一手間加えました。

#!/bin/bash
function test_bk {
  flag=1
  IFS=$'\n'
  for line in $(mount)
  do echo $line | grep /bk | grep "bkvg-bklv" >/dev/null

さて、この修正版を実行してみます。

$ ./test_bk.sh
flag is 0
return flag is 0

これで意図した通りにbashでは動くようになりました。

bashの代わりにkshを使った場合もやっぱり問題は起こって、kshでは$''形式は使えないので、IFS変数に改行をいれないといけません。

...
  IFS='
'
  for ... 

標準入力をブロックする副作用があるって何かで読んだ記憶があるけれど、この場合って"read"が該当するのかなぁ…。

bashの中から"help read"を実行して、説明を読みかえしてみると'-u fd'オプションについて書かれていました。 これを使って、標準入力以外に対してreadコマンドが動くようにして、最初のバージョンでちゃんと動くようにしてみます。

#!/bin/bash
function test_bk {
  flag=1
  tmpfile=$(mktemp) ; exec 3>$tmpfile
  mount 1>&3
  exec 3>&- ; exec 3<$tmpfile
  while read -u 3 line
  do echo $line | grep /bk | grep "bkvg-bklv" >/dev/null
    test "$?" = "0" && flag=0 && echo flag is $flag ## for debug
  done
  exec 3>&- ; rm $tmpfile
  echo return flag is $flag ## for debug
  return $flag
}
if test_bk ; then 
$ ./test_bk.sh
flag is 0
return flag is 0
...

なんとかなったけれど、中間ファイルは増えるし、シンプルとは言い難いなぁ。 どうするのが一番いいんだろう。それにこれじゃkshで動かないんだよね…。

さて、これをkshで動かすために必要な修正は単純に空白をひとつ削除するだけ。

** bash版 **

  while read -u 3 line

** ksh版 **

  while read -u3 line

うーん。bashとkshって似ているんだけれど、細かいところで面倒だなぁ。

2009/10/02追記: コンスタントにアクセスがあるので、読み返したところ体裁がいいかげん過ぎたので直しました。
コード部分に手は加えていません。

0 件のコメント: