サーバ管理者のためのプログラミング入門(シェルプログラミング基礎6~条件分岐その2) [サーバ管理者のプログラミング]
シェルスクリプトで使う「if」構文は、コマンドが正常終了したか(≒終了ステータスが0だったかどうか)で真偽判断を行うということは理解してもらえただろうか。
ここで一つの疑問…というか不満が出る人がいるかもしれない。
一般的なプログラム言語では一般的な、大小比較とか出来ないんですかー?
という問題。
結論からいくと、シェルスクリプトのif構文そのもの単体では出来ないということになる。しかし、幸いにもif構文と組み合わせて大小比較等を行うコマンドが別に用意されているので、これをif構文と共に使えば、目的を成就することができるのであった。
その命令とは、「test」である。
書式は、 test 条件式となっている。
与えられた条件式を満たす場合は、testコマンドの終了ステータスに0が返り、与えられた条件式を満たさない場合には、testコマンドの終了ステータスに0以外が返るのである。結果的に、if構文と組み合わせれば大小比較等が出来る…ということになる。
では、例を見てみよう。
変数「RANDOM」から取得できる0~32767の間の乱数が、16384「以上」であれば『BIG』、それ「未満」であれば『SMALL』と表示するスクリプト(1回しか実行しないのであまり面白みは無いのだけども…)。
実行するたびに数値が変化して、BIGとかSMALLとか表示される。
2点補足しておくと、変数「RANDOM」とはbashが提供している変数で、参照するたびに0~32767の間のランダムな値が得られるという特殊な変数である。なお、『擬似乱数』といわれるようなものであって完全な乱数ではないので注意が必要である。
で、サンプルのシェルスクリプト内では「HENSU=$RANDOM」という記述がある。これは変数「RANDOM」の内容をそのまま変数「HENSU」に代入しているのであるが、これは今しがた説明したとおり、変数「RANDOM」は参照するたびに値が変わってしまうので、一度獲得した値を何度も使用したい場合はどこか他の変数に保存しておかないと、参照するたびに値が変化してしまうので困るための措置。
サンプルの例では、if構文とechoコマンドとで2回参照している。
補足説明が済んだところで今度は肝心のtestコマンドの記述を見てみよう。
「if test $HENSU -ge 16384」という部分が、16384以上かどうかを比較している部分にあたる。これを数学的な表現で表すなら
HENSU ≧ 16384
ということになる。引数にある「-ge」が、数学の記号で言うところの「≧」に該当している。なお、C/C++的表現なら
HENSU >= 16384
ということになる。「-ge」は「>=」に該当しているということ。
最初はこの「-ge」とかの部分の記述方法を間違えやすいので、気をつけるように。それでは、この部分の記述方法を列挙しておく。
「gt」とか「lt」とかなじみがないYO!という人もいるかもしれないが、実はHTMLに慣れ親しんだ人なら逆に違和感無いかもしれない。というのも、HTMLで「>」を記述するとき、「>」と書くからだ。同様に「<」なら「<」だ。ちょうどtestコマンドの記述方法と一緒ではないか。
「ge」「le」の「e」は、条件判断に「=」(イコール)が入るか入らないかで判断できよう。そう。これは「イコール」の「e」なのである。
では。たまには「練習問題」といってみようか。
練習問題1:dateコマンドの出力結果から、現在の時刻の「時」(hour)を取得し、「午前」「午後」の判断をするシェルスクリプトを作成した。testコマンドの記述方法で正しいものを下記の①から⑤の中から選びなさい
testコマンドの選択肢…
① $HOUR -gt 12
② $HOUR -ge 12
③ $HOUR -lt 12
④ $HOUR -le 12
⑤ $HOUR -ne 12
答えは下の方にある「続きを読む」からどうぞ。
次に、if構文を用いて2つの条件を同時に満たす場合、あるいは2つの条件のどちらか一方を満たす場合の条件判断をする方法を紹介する。
たとえば、仕事の勤務時間内かどうかを判断するとしよう。
勤務時間が9時から6時(18時)までの会社に勤務しているとする。このとき、数学的な表現をすれば時刻の「時」(hour)が、
9≦HOUR<18
ということなら、勤務時間内にあるという事ができるだろう。
testコマンドでこれをどう表現するか。これまでの内容を見てきた人はおそらく…
test 9 -le $HOUR -lt 18
と書きたくなるのではないだろうか。
しかし残念ながらこれは「-bash: test: too many arguments」というエラーになる。
このように、変数の値がある範囲内に収まっているかどうか判断したい場合は、数式を2個に分割する必要がある。
9≦HOUR<18
この表現は、
9≦HOUR かつ、 HOUR<18
といい改めることが出来る。「9≦HOUR」と「HOUR<18」とを両方同時に満たせば、結果的に「9≦HOUR<18」を満たすことになるのである。そのような条件式を記述するには、「かつ」にあたる部分を覚えなければならない。それは「-a」である。
test 条件式A -a 条件式B
このように記述すると、条件式Aと条件式Bとを両方同時に満たす場合に、testコマンドの終了コードが0になる。
では、試してみよう。
こんな具合。
なお、判りやすさを優先するために、数学的な表現をそのままtestコマンドの引数に置き換えたが、プログラム的な美しさという観点からすると、条件式の部分は変数→記号→比較する値の順に記述することが一般的には推奨されている。その考え方に基づいて記述すると、上記のスクリプトのtestコマンドは…
if test $HOUR -ge 9 -a $HOUR -lt 18
と書くことになる。正直なところ、シェルスクリプトでそこまで要求されることは少ないだろうが、留意しておくとよいかもしれない。なお、どちらの記述方法でも結果は同じになる。
続いて、2つの条件のどちらか一方を満たす場合を見てみよう。
あなたの会社ではボーナスが6月と12月に支給されるとして、dateコマンドから得た「月」(month)が6月か12月のどちらかである場合に「BONUS!」と表示させる…というスクリプトを作成してみよう。
testコマンドの部分に注目してほしい。
if test $MONTH -eq 6 -o $MONTH -eq 12
「-o」という記述が登場している。この記述が、「または」を表しているのである。
変数「MONTH」には1~12のいずれかの値が入るが、6だった場合には「$MONTH -eq 6」の条件式が成立し、12だった場合には「$MONTH -eq 12」の条件式が成立する。そして、「-o」の指定によってそのどちらか一方が成立すれば、testコマンドの終了ステータスが0になる…という仕組みである。
ところで。「私の会社はボーナスが4月、8月、12月と3回出るんですけど~」という場合はどうすればよいか。変数「MONTH」が4か8か12だったら…という判断が必要になるのである。
このような場合は、実は正直にそのまま記述すれば問題ない。
if test $MONTH -eq 4 -o $MONTH -eq 8 -o $MONTH -eq 12
実は、「-a」や「-o」を使った場合、条件式はいくらでも増やせる(…限度はあるのだが)のであった。
では、最後にもう1個練習問題をやっておこう。
練習問題2:dateコマンドから、時刻の「時」(hour)と曜日(WDAY)を取得して『勤務時間内』かどうかの判定を行いたい。勤務時間は9時から18時までだが、日曜日と土曜日は会社はお休みである。なお、変数「WDAY」には0~6の数値が入り、0が日曜日、1が月曜日…6が土曜日となっている。testコマンドにはどのように記述すればよいか、考えてもらいたい。
答えは下の方にある「続きを読む」からどうぞ。
練習問題1 の 回答:
正解は「③」。
解説:
シェルスクリプトをよくみると、if構文の後ろは、testコマンドの終了ステータスが0の場合(つまり、条件が成立した場合)に「AM」と表示し、そうでない場合は「PM」と表示している。よって、testコマンドは「時刻が午前中か?」という判定をすることになる。
一方、「午前中」とは時刻の「時」(hour)が 0~11 の範囲にあれば「午前中」であるといえるが、選択肢に挙げられている判断の基準の数値は「12」なので、変数「HOUR」が午前中かどうか判断するには、数学的な表現では
HOUR < 12
という条件を満たす必要がある。この「<」にあたる部分は、表にあるように「-lt」なので、「③」の「$HOUR -lt 12」が正解ということになる。
練習問題2の回答例:
$HOUR -ge 9 -a $HOUR -lt 18 -a $WDAY -ge 1 -a $WDAY -le 5
解説:
時刻が9時から18時の間…というのは紹介済みだしすぐわかるとおもう。問題は曜日。
変数「WDAY」は0が日曜日で1が月曜日…5が金曜日、6が土曜日になるので、変数「WDAY」の中身が1以上5以下ならその日は勤務日ということになる。つまり、これを数学的な表現で表すと…
9≦HOUR<18 なおかつ、 1≦WDAY≦5
ということになる。この2つの条件を両方同時に満たせば、その時間は勤務中ということになる。
変数「HOUR」について、testコマンド用の条件式に書き直すと…
9 -le $HOUR -a $HOUR -lt 18
(「プログラム的に美しい」記述方法なら $HOUR -ge 9 -a $HOUR -lt 18)
そして、変数「WDAY」について、testコマンド用の条件式に書き直せば、
1 -le $WDAY -a $WDAY -le 5
(「プログラム的に美しい」記述方法なら $WDAY -ge 1 -a $WDAY -le 5)
ということに。そして、これを両方同時に満たす必要があるので、この1件をさらに「-a」でくっつけてしまうのである。
9 -le $HOUR -a $HOUR -lt 18 -a 1 -le $WDAY -a $WDAY -le 5
これを「プログラム的に美しい」記述方法にすると、本回答案のようになるのである。
ところで。さらに別の回答としては、
$HOUR -ge 9 -a $HOUR -lt 18 -a $WDAY -ne 0 -a $WDAY -ne 6
という回答案も考えられる。これは、
時刻が9時~18時の間で、なおかつ日曜日でも土曜日でもないということを言っている。このような記述をしても同じ結果が得られる。
ここで一つの疑問…というか不満が出る人がいるかもしれない。
一般的なプログラム言語では一般的な、大小比較とか出来ないんですかー?
という問題。
結論からいくと、シェルスクリプトのif構文そのもの単体では出来ないということになる。しかし、幸いにもif構文と組み合わせて大小比較等を行うコマンドが別に用意されているので、これをif構文と共に使えば、目的を成就することができるのであった。
その命令とは、「test」である。
書式は、 test 条件式となっている。
与えられた条件式を満たす場合は、testコマンドの終了ステータスに0が返り、与えられた条件式を満たさない場合には、testコマンドの終了ステータスに0以外が返るのである。結果的に、if構文と組み合わせれば大小比較等が出来る…ということになる。
では、例を見てみよう。
変数「RANDOM」から取得できる0~32767の間の乱数が、16384「以上」であれば『BIG』、それ「未満」であれば『SMALL』と表示するスクリプト(1回しか実行しないのであまり面白みは無いのだけども…)。
#!/bin/sh HENSU=$RANDOM if test $HENSU -ge 16384 then # 16384以上 echo $HENSU" BIG" else # 16384未満 echo $HENSU" SMALL" fi
実行するたびに数値が変化して、BIGとかSMALLとか表示される。
2点補足しておくと、変数「RANDOM」とはbashが提供している変数で、参照するたびに0~32767の間のランダムな値が得られるという特殊な変数である。なお、『擬似乱数』といわれるようなものであって完全な乱数ではないので注意が必要である。
で、サンプルのシェルスクリプト内では「HENSU=$RANDOM」という記述がある。これは変数「RANDOM」の内容をそのまま変数「HENSU」に代入しているのであるが、これは今しがた説明したとおり、変数「RANDOM」は参照するたびに値が変わってしまうので、一度獲得した値を何度も使用したい場合はどこか他の変数に保存しておかないと、参照するたびに値が変化してしまうので困るための措置。
サンプルの例では、if構文とechoコマンドとで2回参照している。
補足説明が済んだところで今度は肝心のtestコマンドの記述を見てみよう。
「if test $HENSU -ge 16384」という部分が、16384以上かどうかを比較している部分にあたる。これを数学的な表現で表すなら
HENSU ≧ 16384
ということになる。引数にある「-ge」が、数学の記号で言うところの「≧」に該当している。なお、C/C++的表現なら
HENSU >= 16384
ということになる。「-ge」は「>=」に該当しているということ。
最初はこの「-ge」とかの部分の記述方法を間違えやすいので、気をつけるように。それでは、この部分の記述方法を列挙しておく。
数学的表現 | C/C++的表現 | testコマンド的表現 |
---|---|---|
= | == | -eq |
≠ | != | -ne |
> | > | -gt |
≧ | >= | -ge |
< | < | -lt |
≦ | <= | -le |
「gt」とか「lt」とかなじみがないYO!という人もいるかもしれないが、実はHTMLに慣れ親しんだ人なら逆に違和感無いかもしれない。というのも、HTMLで「>」を記述するとき、「>」と書くからだ。同様に「<」なら「<」だ。ちょうどtestコマンドの記述方法と一緒ではないか。
「ge」「le」の「e」は、条件判断に「=」(イコール)が入るか入らないかで判断できよう。そう。これは「イコール」の「e」なのである。
では。たまには「練習問題」といってみようか。
練習問題1:dateコマンドの出力結果から、現在の時刻の「時」(hour)を取得し、「午前」「午後」の判断をするシェルスクリプトを作成した。testコマンドの記述方法で正しいものを下記の①から⑤の中から選びなさい
#!/bin/sh HOUR=`date '+%H'` if test (ここになんて記述すればよい?) then # 午前 echo "AM" else # 午後 echo "PM" fi
testコマンドの選択肢…
① $HOUR -gt 12
② $HOUR -ge 12
③ $HOUR -lt 12
④ $HOUR -le 12
⑤ $HOUR -ne 12
答えは下の方にある「続きを読む」からどうぞ。
次に、if構文を用いて2つの条件を同時に満たす場合、あるいは2つの条件のどちらか一方を満たす場合の条件判断をする方法を紹介する。
たとえば、仕事の勤務時間内かどうかを判断するとしよう。
勤務時間が9時から6時(18時)までの会社に勤務しているとする。このとき、数学的な表現をすれば時刻の「時」(hour)が、
9≦HOUR<18
ということなら、勤務時間内にあるという事ができるだろう。
testコマンドでこれをどう表現するか。これまでの内容を見てきた人はおそらく…
test 9 -le $HOUR -lt 18
と書きたくなるのではないだろうか。
しかし残念ながらこれは「-bash: test: too many arguments」というエラーになる。
このように、変数の値がある範囲内に収まっているかどうか判断したい場合は、数式を2個に分割する必要がある。
9≦HOUR<18
この表現は、
9≦HOUR かつ、 HOUR<18
といい改めることが出来る。「9≦HOUR」と「HOUR<18」とを両方同時に満たせば、結果的に「9≦HOUR<18」を満たすことになるのである。そのような条件式を記述するには、「かつ」にあたる部分を覚えなければならない。それは「-a」である。
test 条件式A -a 条件式B
このように記述すると、条件式Aと条件式Bとを両方同時に満たす場合に、testコマンドの終了コードが0になる。
では、試してみよう。
#!/bin/sh HOUR=`date '+%H'` if test 9 -le $HOUR -a $HOUR -lt 18 then # 勤務時間内 echo "at Work" else # 勤務時間外 echo "in Private" fi
こんな具合。
なお、判りやすさを優先するために、数学的な表現をそのままtestコマンドの引数に置き換えたが、プログラム的な美しさという観点からすると、条件式の部分は変数→記号→比較する値の順に記述することが一般的には推奨されている。その考え方に基づいて記述すると、上記のスクリプトのtestコマンドは…
if test $HOUR -ge 9 -a $HOUR -lt 18
と書くことになる。正直なところ、シェルスクリプトでそこまで要求されることは少ないだろうが、留意しておくとよいかもしれない。なお、どちらの記述方法でも結果は同じになる。
続いて、2つの条件のどちらか一方を満たす場合を見てみよう。
あなたの会社ではボーナスが6月と12月に支給されるとして、dateコマンドから得た「月」(month)が6月か12月のどちらかである場合に「BONUS!」と表示させる…というスクリプトを作成してみよう。
#!/bin/sh MONTH=`date '+%m'` if test $MONTH -eq 6 -o $MONTH -eq 12 then # ボーナス月 echo "BONUS!" fi
testコマンドの部分に注目してほしい。
if test $MONTH -eq 6 -o $MONTH -eq 12
「-o」という記述が登場している。この記述が、「または」を表しているのである。
変数「MONTH」には1~12のいずれかの値が入るが、6だった場合には「$MONTH -eq 6」の条件式が成立し、12だった場合には「$MONTH -eq 12」の条件式が成立する。そして、「-o」の指定によってそのどちらか一方が成立すれば、testコマンドの終了ステータスが0になる…という仕組みである。
ところで。「私の会社はボーナスが4月、8月、12月と3回出るんですけど~」という場合はどうすればよいか。変数「MONTH」が4か8か12だったら…という判断が必要になるのである。
このような場合は、実は正直にそのまま記述すれば問題ない。
if test $MONTH -eq 4 -o $MONTH -eq 8 -o $MONTH -eq 12
実は、「-a」や「-o」を使った場合、条件式はいくらでも増やせる(…限度はあるのだが)のであった。
では、最後にもう1個練習問題をやっておこう。
練習問題2:dateコマンドから、時刻の「時」(hour)と曜日(WDAY)を取得して『勤務時間内』かどうかの判定を行いたい。勤務時間は9時から18時までだが、日曜日と土曜日は会社はお休みである。なお、変数「WDAY」には0~6の数値が入り、0が日曜日、1が月曜日…6が土曜日となっている。testコマンドにはどのように記述すればよいか、考えてもらいたい。
#!/bin/sh HOUR=`date '+%H'` WDAY=`date '+%w'` if test (この部分に適切な記述をしてもらいたい) then # 勤務時間内 echo "at Work" else # 勤務時間外 echo "in Private" fi
答えは下の方にある「続きを読む」からどうぞ。
練習問題1 の 回答:
正解は「③」。
解説:
シェルスクリプトをよくみると、if構文の後ろは、testコマンドの終了ステータスが0の場合(つまり、条件が成立した場合)に「AM」と表示し、そうでない場合は「PM」と表示している。よって、testコマンドは「時刻が午前中か?」という判定をすることになる。
一方、「午前中」とは時刻の「時」(hour)が 0~11 の範囲にあれば「午前中」であるといえるが、選択肢に挙げられている判断の基準の数値は「12」なので、変数「HOUR」が午前中かどうか判断するには、数学的な表現では
HOUR < 12
という条件を満たす必要がある。この「<」にあたる部分は、表にあるように「-lt」なので、「③」の「$HOUR -lt 12」が正解ということになる。
練習問題2の回答例:
$HOUR -ge 9 -a $HOUR -lt 18 -a $WDAY -ge 1 -a $WDAY -le 5
解説:
時刻が9時から18時の間…というのは紹介済みだしすぐわかるとおもう。問題は曜日。
変数「WDAY」は0が日曜日で1が月曜日…5が金曜日、6が土曜日になるので、変数「WDAY」の中身が1以上5以下ならその日は勤務日ということになる。つまり、これを数学的な表現で表すと…
9≦HOUR<18 なおかつ、 1≦WDAY≦5
ということになる。この2つの条件を両方同時に満たせば、その時間は勤務中ということになる。
変数「HOUR」について、testコマンド用の条件式に書き直すと…
9 -le $HOUR -a $HOUR -lt 18
(「プログラム的に美しい」記述方法なら $HOUR -ge 9 -a $HOUR -lt 18)
そして、変数「WDAY」について、testコマンド用の条件式に書き直せば、
1 -le $WDAY -a $WDAY -le 5
(「プログラム的に美しい」記述方法なら $WDAY -ge 1 -a $WDAY -le 5)
ということに。そして、これを両方同時に満たす必要があるので、この1件をさらに「-a」でくっつけてしまうのである。
9 -le $HOUR -a $HOUR -lt 18 -a 1 -le $WDAY -a $WDAY -le 5
これを「プログラム的に美しい」記述方法にすると、本回答案のようになるのである。
ところで。さらに別の回答としては、
$HOUR -ge 9 -a $HOUR -lt 18 -a $WDAY -ne 0 -a $WDAY -ne 6
という回答案も考えられる。これは、
時刻が9時~18時の間で、なおかつ日曜日でも土曜日でもないということを言っている。このような記述をしても同じ結果が得られる。
2010-02-01 12:14
nice!(0)
コメント(0)
トラックバック(0)
コメント 0