So-net無料ブログ作成
検索選択
サーバ管理者のプログラミング ブログトップ
前の10件 | -

サーバ管理者のためのプログラミング入門(シェルプログラミング基礎12~ループ処理その3) [サーバ管理者のプログラミング]

 for構文の次にうつる。

 今度はwhile構文とuntil構文。この二つの構文は基本的な動作は同じで、ちょっとだけ違う振る舞いをする。

while コマンド
do
    繰り返し処理
done

until コマンド
do
    繰り返し処理
done

 この2つの構文は、
 ① 「コマンド」を実行・評価する
 ② 繰り返し処理を1回実行する
 ③ 「①」に戻る

 という動作は一緒。ただし、『① 「コマンド」を実行・評価する』という、「評価」の部分で挙動が異なる。

 つまり、
    while構文の場合  コマンドがなら繰り返し処理を実行
    until構文の場合  コマンドがなら繰り返し処理を実行
 ということに。

 それでは、簡単なサンプルから。
#!/bin/sh

HENSU=$RANDOM

while test $HENSU -lt 30000
do
        echo $HENSU
        HENSU=$RANDOM
done

echo "LAST "$HENSU


 以前、ちょっと登場した変数RANDOMから、乱数を取り出しているが、その取り出した乱数が30000未満だった場合に、処理が繰り返されている。
 変数RANDOMの内容を変数HENSUに取り出しているのは、参照するたびに値が変わってしまうため。(これも以前のサンプルで説明している。)

 実行するとこんな感じになる。(実行するたびに結果は異なる)
[root@test791 tmp]# ./sample.sh
20737
14570
27332
8550
5638
3829
LAST 30753

 一番最後の行の「LAST 30753」を表示しているのはwhile構文の下(外)にあるechoコマンドだ。この結果は「30000未満」という条件を満たしていないので、while構文の中身は全く実行されない。よって、最後のechoが無ければこの表示がされないことに注意してもらいたい。

 なお、while構文は人間が手動で処理を止めるまで、延々と繰り返し処理をしたい場合にも使われる。(これを「永久ループ」とか「無限ループ」とも)
 例えば、30秒おきにnetstat -iの情報を表示させて、ネットワークインタフェースの状態を確認したい時とか、
while :
do
    netstat -i
    sleep 30
done

 こんな具合に。「while」に続けて「:」と記述しているが、これは「常に真」を意味する。whileの評価式が常に真ということなので、永遠に処理を繰り返す…ということになる。止めたいときはCtrl+Cとかkillするとかしてもらいたい。

 untilを使ったサンプル。
 とあるサーバ(停止中)に対してこれから電源を投入して起動するが、起動するまで待つ…という場合。pingに反応したら「起動した」と判断している。(手抜き)なお、チェックは30秒ごとに試みている
until ping -c 1 SERVER > /dev/null 2>&1
do
    sleep 30
done


サーバ管理者のためのプログラミング入門(シェルプログラミング基礎11~ループ処理その2) [サーバ管理者のプログラミング]

 forループ構文には、前アーティクルに示したような書式の他にもう一つ形式がある。それは、C言語をはじめとする一般的な言語で当然のように備わっている書式である。
 ただし、シェルスクリプトである都合上、書き方が微妙に違うのでそこだけ要注意。

 for ((評価式1 ; 評価式2 ; 評価式3 ))
do
    繰り返し処理
 done

 評価式1、評価式2、評価式3   では判りづらいと思うので、まずはサンプルを見てもらおう。
#!/bin/sh

for (( I=0 ; I<10 ; I++ ))
do
    echo $I
done


 上記の処理を実行するとこうなる。
[root@mon002 tmp]# for (( I=0 ; I<10 ; I++ ))
> do
> echo $I
> done
0
1
2
3
4
5
6
7
8
9


 変数Iの内容が0から9まで変化していることがわかるだろうか。

 それでは、解説する。

 「((」と「))」とで囲まれた部分にある3つの「評価式」は、比較的C/C++的な記述方法が使える。exprコマンド的ではないことに注意する。

 まず、シェルスクリプトの実行がこのfor構文に差し掛かると、最初に「評価式1」が評価される。上記サンプルでは「I=0」となっている部分。その「評価式」が意味しているのは、「変数Iに0を代入」と、いう意味である。

 続いて、「評価式2」が評価される。上記サンプルでは「I<10」と、なっている部分。この「評価式」が意味しているのは、「変数Iが10未満であるか」と、いう意味である。
 なお、この「評価式2」の評価結果がの(条件式が成立しない)場合には、これ移行の処理や評価は行わず、for構文を終了してしまう。

 「評価式2」がの(条件式が成立した)場合には、次の「do」と「done」とで囲まれた「繰り返し処理」の部分が1回実行される。

 処理がdoneまでたどり着くと、こんどは「評価式3」が評価される。上記サンプルでは「I++」と、なっている部分。この「評価式」が意味しているのは「変数Iの内容をインクリメント(1加算)しなさい」という意味になる。

 整理すると

 ① 「評価式1」を評価する
 ② 「評価式2」を評価する
    真? → ③へ
    偽? → for構文終了
 ③ 繰り返し処理部分を1回実行する
 ④ doneまで到着
 ⑤ 「評価式3」を評価する
 ⑥ 「②」まで戻る

 ということ。上記サンプルの場合だと…

 ① 変数Iに0を代入する
 ② 変数Iは10未満ですか?
    結果は「真」
 ③ echo $Iを実行する。変数Iは0なので、「echo 0」が実行される
 ④ doneまで到着
 ⑤ 変数Iの内容をインクリメント(1加算)する。今は変数Iの内容は0なので、1加算して1になる
 ↓
 ② 変数Iは10未満ですか?
    結果は「真」
 ③ echo $Iを実行する。変数Iは1なので、「echo 1」が実行される
 ④ doneまで到着
 ⑤ 変数Iの内容をインクリメント(1加算)する。今は変数Iの内容は1なので、1加算して2になる
 ↓
 ② 変数Iは10未満ですか?
    結果は「真」
 :
 :(省略)
 :
 ③ echo $Iを実行する。変数Iは9なので、「echo 9」が実行される
 ④ doneまで到着
 ⑤ 変数Iの内容をインクリメント(1加算)する。今は変数Iの内容は9なので、1加算して10になる
 ↓
 ② 変数Iは10未満ですか?
    結果は「偽」なので、for構文を終了する

 ということが実行されている。

 なお、このfor構文の場合、変数を繰り返し処理の中で用いてもいいし、用いずに単に「X回繰り返す」という目的でだけ用いられることもある。上記サンプルでは思いっきり変数の中身を表示しているが、
#!/bin/sh

echo "so"
for (( I=0 ; I<6 ; I++ ))
do
    echo "Shining Shiner Shinyest"
    echo "Girls be ambitious & shine"
    echo ""
done

 とかいう感じで、上記サンプルの場合は変数Iには全く触れずにechoコマンドだけを6回繰り返している。

 ちなみに、2つのサンプルで例示しているfor構文。変数は何でも構わないことは言うまでも無いが、2つのサンプルで示している書き方は多用される基本形なので丸暗記してしまうことをお勧めする。
 なお、1個目のサンプルでは10回の繰り返しを実行するのに「0~9」の10回としているが、「1~10」で10回繰り返したい場合は、「for (( I=1 ; I<=10 ; I++ ))」とする。


 では、もう一つサンプルとして、「掛け算九九」の答えを表示するサンプルを示す。
 変数Iと変数Jとを用いてfor構文を入れ子にしている。
#!/bin/sh

for (( I=1 ; I<=9 ; I++ ))
do
        for (( J=1 ; J<=9 ; J++ ))
        do
                KOTAE=`expr $I \* $J`
                printf " %2d " $KOTAE
        done
        echo ""
done

 このシェルスクリプトを実行すると…
[root@test791 tmp]# ./kuku.sh
  1   2   3   4   5   6   7   8   9
  2   4   6   8  10  12  14  16  18
  3   6   9  12  15  18  21  24  27
  4   8  12  16  20  24  28  32  36
  5  10  15  20  25  30  35  40  45
  6  12  18  24  30  36  42  48  54
  7  14  21  28  35  42  49  56  63
  8  16  24  32  40  48  56  64  72
  9  18  27  36  45  54  63  72  81

 こんな具合になる。

サーバ管理者のためのプログラミング入門(シェルプログラミング基礎10~ループ処理その1) [サーバ管理者のプログラミング]

 久々のエントリー。

 条件分岐の次は「ループ処理」に進む。まず、シェルスクリプトを書く上で結構便利なループ方法から。

 for 変数 in パラメータ1 ...
 do
    繰り返し処理
 done

 もうちょっと具体的にサンプルを示すとこんな感じ
#!/bin/sh

for SITE in www.google.co.jp www.yahoo.co.jp www.goo.ne.jp www.excite.co.jp
do
    ping -c 1 $SITE
done


 内容としては、
  ・www.google.co.jp
  ・www.yahoo.co.jp
  ・www.goo.ne.jp
  ・www.excite.co.jp
 の4つのサイトにpingを1回ずつ投げる…という程度のもの。
 最初の「for」コマンドの行、「in」の後ろにはスペース(TABなどでもよい)で区切った文字列を並べると、それを1個目から順次変数(サンプルでは変数SITE)に代入して、「do」~「done」に囲まれた処理ブロックを1回ずつ実行するというもの。
 上記のサンプルでは、
 ①変数SITEに「www.google.co.jp」を代入する
 ②ping -c www.google.co.jp を実行する
 ③変数SITEに「www.yahoo.co.jp」を代入する
 ④ping -c www.yahoo.co.jp を実行する
 ⑤変数SITEに「www.goo.ne.jp」を代入する
 :
 :
 という感じになる。

 例えば、複数のサーバに同じ処理をリモートで実行したい場合等に便利。
 server1、server2、server3、server4、server5にそれぞれ「yum update」を実行したいという状況なら
#!/bin/sh

for SERVER in server1 server2 server3 server4 server5
do
    ssh $SERVER yum -y update
done

 とかすれば、いちいちログインして実行…とかしなくてもよい。(sshの鍵交換が済んでいないなら、いちいちパスワードは聞かれてしまうが。)
 なお、for文のin句の後ろに並ぶ文字列は、左側から逐次処理されていくので、繰り返し処理が番号順でなく例えば「一番最初にserver5」を処理しなければならない場合等は「for SERVER in server5 server1 server2 server3 server4」とか記述することも可能。


 それでは、上の「server1」~「server5」に連続してyum updateを実行するスクリプトを作成するとして、「サーバが生きていることを確認し、pingが通ったサーバにだけyum updateを実行したい…という処理を行ってみる。

 いきなりサンプルを示すと、以下のようになる。
#!/bin/sh

for SERVER in server1 server2 server3 server4 server5
do
    if ping -c 1 $SERVER > /dev/null 2>&1
    then
        #pingが通った場合の処理
        ssh $SERVER yum -y update
    fi
done


 まず、「for SERVER in server1 server2...」の行。
 これはこのアーティクルで説明しているとおり、繰り返し処理を行うことを意味している。変数SERVERに「server1」、「server2」…と順次代入されていく。その繰り返し行われる処理は、次の行「do」から「done」までの間の処理が対象となる。

 「if ping -c 1 $SERVER > /dev/null 2>&1」は、変数SERVERに代入されている文字列(要するにサーバの名前)の示すサーバに対してpingを実行している。なお、pingはいろいろ表示されてうっとうしいので、/dev/nullにpingの実行結果を捨てている。
 このpingが正常終了すると、次の行「then」から「fi」までの間の処理が行われ、pingが異常終了した場合(pingが通らないとか、サーバ名が解決できない場合)は、else句が無いので特に何も実行しない。

 このように、forやifの処理はブロックの中にそれぞれ入れ子にすることも可能である。

サーバ管理者のためのプログラミング入門(シェルプログラミング基礎9~条件分岐その5) [サーバ管理者のプログラミング]

 ここまでの解説で、コマンドの実行結果に応じて、あるいは変数の中身を判定した結果に応じて、またはファイルの特性を判定した結果に応じて処理を分岐する方法について理解してもらえたと思うが、ここまでの「条件分岐」は、シロかクロかという二者択一の処理分岐だった。
 では、変数の中身に応じて複数の処理に分岐したいという場合はどうすればよいだろうか。

 方法の一つとしては、if構文を羅列する方法がある。まずはこの点について説明を付け加えておく。

 ここまでに説明したif構文の書式は、

 if コマンドA then
    真だった場合の処理
 fi

 または、

 if コマンドA then
    真だった場合の処理
 else
    偽だった場合の処理
 fi

 という形だった。だから、コマンドAが正常だったかエラーだったか(真だったか偽だったか)という分岐だけしか出来なかった。

 ところが、時と場合によってはコマンドAの結果(具体的にはtestコマンド)で判定する変数の中身がシロかクロか…だけでなく、「1」だったら…「2」だったら…「3」だったら…という具合に処理を振り分けたいことも考えられる。
 このような場合にこれまでのif構文を使った知識で克服するとなると、こう書けばよいことになる。

 if test 変数 -eq 1 then
    変数が1だったときの処理
fi
 if test 変数 -eq 2 then
    変数が2だったときの処理
fi
 if test 変数 -eq 3 then
    変数が3だったときの処理
fi

 このように、if構文を羅列してしまう方法がまず考えられる。
 しかしこの場合では変数が1だった場合、2以降の判定をすることが全くのムダになることが考えられる。変数が2だった場合の判定は、あくまでも「変数が1ではなかった」時にすればよいのではないか…と考えることもできる。
 と、なると、このように記述を改めることもできる。

 if test 変数 -eq 1 then
    変数が1だったときの処理
 else
    if test 変数 -eq 2 then
        変数が2だったときの処理
    else
       if test 変数 -eq 3 then
          変数が3だったときの処理
       fi
    fi
 fi

 こうすれば、変数が1のときは「変数が1だったときの処理」を実行するが、2以降かどうかの判定は行わないし、変数が2のときは「変数が2だったときの処理」を実行するが3かどうかの判定は行わないことになり、処理の効率が向上する。
 ただし、判断する件数が増えるとそれに伴ってif構文の「入れ子」が深くなってしまいスクリプトは見づらいものになってしまうということが容易に想像できると思う。

 で、if構文にはこれに対処する方法がある。「else」句に「if」句を合体させた、「elif」という記述方法があるのであった。これを用いると、上記のif構文の固まりは次のように書き改めることが出来る。

 if test 変数 -eq 1 then
    変数が1だったときの処理
 elif test 変数 -eq 2 then
    変数が2だったときの処理
 elif test 変数 -eq 3 then
    変数が3だったときの処理
 else
    変数が1でも2でも3でもなかったときの処理
 fi

 このように、fiでif構文を閉じるのが1回で済むようになり、記述性がちょっぴり向上するというもの。最初のうちは感覚的に掴みにくいかもしれないけどね。

 で、これよりももっと良い方法が有るのでそちらを紹介する。
 単に変数の中身が1だったら…2だったら…3だったら…というようなケースで処理を複数パターン分岐させたい場合に有効なのが、「case構文」である。
 サンプルをみてもらいたい。シェルスクリプトに仕立てて有る。
#!/bin/sh

if [ -z "$1" ]
then
        echo "色番号(0~8)を指定してください。"
        exit 1
fi

case    $1      in
        0 )     echo "黒"
                ;;
        1 )     echo "青"
                ;;
        2 )     echo "赤"
                ;;
        3 )     echo "紫"
                ;;
        4 )     echo "緑"
                ;;
        5 )     echo "水色"
                ;;
        6 )     echo "黄色"
                ;;
        7 )     echo "白"
                ;;
        * )     echo "色番号が正しい範囲ではありません"
esac


 「case」にはじまり、「esac」に終わる固まりが、その「case構文」にあたる。変数「$1」(シェルスクリプトの位置パラメータ)の内容に応じて処理を分岐している。0なら「黒」、1なら「青」、2なら「赤」…で7だったら「白」と表示している。最後の「*」の部分は、0~7のどれにも当てはまらない場合に実行される部分になっている。

 まずはこのスクリプトを実行してみよう。
[root@kagami tmp]# ./degital_colors.sh 4
緑
[root@kagami tmp]# ./degital_colors.sh 6
黄色
[root@kagami tmp]# ./degital_colors.sh 7
白
[root@kagami tmp]# ./degital_colors.sh 9
色番号が正しい範囲ではありません

 シェルスクリプトのパラメータとして渡した数値に応じて表示される内容が変化していることが判る。
 case構文の、in句の後ろに、パターンと処理の内容が記述されるが、小カッコ閉じる記号「)」の前(左側)に、パターンを記述する。このパターンとマッチすると、小カッコ閉じる記号「)」の後ろ側(右側)からの処理が実行され、末尾の「esac」句に到達するか、またはセミコロン2個「;;」の記述に到達するまでが実行される。パターンのマッチングは上から順に行われるため、どこか途中のパターンにマッチすると、それ以降のマッチングは一切行われない。
 パターンの部分に「*」を記述すると、そこには全てのパターンがマッチすることになる。通常は個別のマッチングを行った後、一番最後に記述する。(理由は判りますか?)通常は、このブロックに「どのパターンにもマッチしなかった場合」の処理を書くのが通例である。そうした処理が必要ない場合はこのブロックを省略しても構わない。どれにもマッチしなかった場合はcase構文は何もせず構文を終了して次のコマンドを実行することになる。

 ところで。たとえば、「変数の中身が1か3だったら○○の処理を、2か4だったら▲▲の処理を…」みたいなケースはどうするか。1と3とで同じことを、2と4とで同じことを記述しなければならないのか?という疑問のぶつかるかもしれない。
 結論から行くとそんなことはない。

 サンプルとして、1~12の「月」の値を指定すると、その月が何日有るかを教えてくれるスクリプトを紹介する。なお、面倒くさいしまだ説明していない部分が有るという諸般の事情にかんがみ、うるう年には非対応なのでそこんとこよろしく。(笑)
#!/bin/sh

if [ -z "$1" ]
then
        echo "1~12の間の値を指定してください。"
        exit 1
fi

case    $1      in
        1 | 3 | 5 | 7 | 8 | 10 | 12 )
                echo "31日ある月です"
                ;;
        4 | 6 | 9 | 11 )
                echo "30日ある月です"
                ;;
        2 )
                echo "28日ある月です"
                ;;
        * )     echo "正しい範囲の引数ではありません。"
esac

 上記の例を見てもらうと判ると思うが、シェルスクリプトの引数に指定された値が、1か3か5か7か8か10か12だったら、「31日ある月です」と表示する処理を、4か6か9か11だったら「30日ある月です」と表示する処理を、2だったら「28日ある月です」と表示する処理をそれぞれ行っている。値は12種類あるが処理の内容は3種類だ。
 マッチするパターンを「|」記号で区切って列挙することで、if構文で言うところのOR条件のようなものを設定することが可能となるのであった。

 なお、このサンプルを実行すると…
[root@Welsper tmp]# ./nissu.sh 1
31日ある月です
[root@Welsper tmp]# ./nissu.sh 3
31日ある月です
[root@Welsper tmp]# ./nissu.sh 4
30日ある月です
[root@Welsper tmp]# ./nissu.sh 2
28日ある月です
[root@Welsper tmp]# ./nissu.sh 13
正しい範囲の引数ではありません。

 こんな具合になる。

サーバ管理者のためのプログラミング入門(シェルプログラミング基礎8~条件分岐その4) [サーバ管理者のプログラミング]

 testコマンドは、これまで説明した数値の大小比較や文字列の比較の他に、ユニークな機能を持っている。それは、ファイル等の形式やパーミッションを判定するという機能である。これは地味に便利なのでサーバ管理者であれば知っていると多いに活用できる機能だ。

1.ファイル等の形式を判定する機能

 まずは、ファイルの形式を判定する機能から紹介する。「」と強調しているのは、ファイル以外にもディレクトリやシンボリックリンク、スペシャルファイル、パイプ、ソケットなど何でもござれだから。ただ、よく使うのは以下の3つだろうか?

 -f ファイル名 : 「ファイル名」に記述されたファイルが存在し、かつそのファイルが通常のファイルであれば真
 -d ディレクトリ名 : 「ディレクトリ名」に記述されたディレクトリが存在し、かつそれがディレクトリであれば真
 -L ファイル名 : 「ファイル名」に記述されたファイルが存在し、かつそのファイルがシンボリックリンクであれば真

 他にもあるが、あまり使わないと思うので説明は割愛している。
 まず、-fとか-dとか-Lとかのオプションの後ろに指定する名前のファイル(やディレクトリやシンボリックリンク)が存在しない場合は、そもそも「偽」という判定が返ってくる。
[root@kagami tmp]# ls -la
合計 24
drwxrwxrwt  4 root root 4096  3月  8 17:35 .
drwxr-xr-x 22 root root 4096  3月  8 13:59 ..
drwxrwxrwt  2 root root 4096  3月  8 13:59 .ICE-unix
drwxr-xr-x  2 root root 4096  3月  8 17:34 directory
-rw-r--r--  1 root root    0  3月  8 17:34 file
lrwxrwxrwx  1 root root    4  3月  8 17:35 sym-link -> file

 このようなディレクトリで「unknown」という名前を指定してtestコマンドを実行してみよう。
[root@kagami tmp]# test -f unknown
[root@kagami tmp]# echo $?
1
[root@kagami tmp]# test -d unknown
[root@kagami tmp]# echo $?
1
[root@kagami tmp]# test -L unknown
[root@kagami tmp]# echo $?
1

 「unknown」という名前のファイルもディレクトリもシンボリックリンクも存在しないので、どれも実行結果が0でない(=「偽」)。

 「file」というファイルについて同様に実行してみると…
[root@kagami tmp]# test -f file
[root@kagami tmp]# echo $?
0
[root@kagami tmp]# test -d file
[root@kagami tmp]# echo $?
1
[root@kagami tmp]# test -L file
[root@kagami tmp]# echo $?
1

 「file」という名前の通常のファイルは存在する。よって、-fオプションの判定結果だけ実行結果が0(=「真」)に、それ以外は0でない(=「偽」)。

 同様に、「directory」について実行してみよう。
[root@kagami tmp]# test -f directory
[root@kagami tmp]# echo $?
1
[root@kagami tmp]# test -d directory
[root@kagami tmp]# echo $?
0
[root@kagami tmp]# test -L directory
[root@kagami tmp]# echo $?
1

 と、このようにディレクトリもちゃんと判定されている。
 シンボリックリンクも同様の結果が得られることを確認しておこう。
[root@kagami tmp]# test -f sym-link
[root@kagami tmp]# echo $?
0
[root@kagami tmp]# test -d sym-link
[root@kagami tmp]# echo $?
1
[root@kagami tmp]# test -L sym-link
[root@kagami tmp]# echo $?
0

 おや!?と思うだろう。-dが偽で、-Lが真になるのは判る。しかし-fも真になっている。実は、ヒントがすでに示されていて…
-rw-r--r--  1 root root    0  3月  8 17:34 file
lrwxrwxrwx  1 root root    4  3月  8 17:35 sym-link -> file

 これは、そのシンボリックリンクが指し示している先がファイルだからこういう結果になっているのであった。
 だから、ディレクトリに対してシンボリックリンクを張ると実行結果も変わってくる。
[root@kagami tmp]# ln -s directory sym-link2
[root@kagami tmp]# ls -la
合計 24
drwxr-xr-x  2 root root 4096  3月  8 17:34 directory
lrwxrwxrwx  1 root root    9  3月  8 17:44 sym-link2 -> directory

 と、ディレクトリに対するシンボリックリンクを作成し、これを判定してみると…
[root@kagami tmp]# test -f sym-link2
[root@kagami tmp]# echo $?
1
[root@kagami tmp]# test -d sym-link2
[root@kagami tmp]# echo $?
0
[root@kagami tmp]# test -L sym-link2
[root@kagami tmp]# echo $?
0

 -Lオプションと-dオプションの判定結果が「真」に、-fオプションの判定結果が「偽」に変化した。


2.ファイルなどのパーミッションを判定する機能

 続いて、ファイルなどのパーミッションを判定する機能を紹介する。
 いくつか判定オプションが存在するが、その中でも比較的よく使うであろうオプションのみ紹介する。

 -r ファイル名 : 「ファイル名」に指定されたファイル等が存在し、かつ読み取りが許可されていれば真
 -w ファイル名 : 「ファイル名」に指定されたファイル等が存在し、かつ書き込みが許可されていれば真
 -x ファイル名 : 「ファイル名」に指定されたファイル等が存在し、かつ実行可能(ディレクトリの場合は進入可能)であれば真

 なお、これらのオプションはパーミッションを判定するのであってファイルの中には直接触らないので、読み取り権限がないファイル等でもエラーになったりしない。

 では、試してみよう。以下のようなファイルやディレクトリを準備した。
[piro791@kagami tmp]$ ls -la
drwx------  2 root root 4096  3月  8 17:59 directory700
drwxrwxrwx  2 root root 4096  3月  8 17:59 directory777
-r--------  1 root root    0  3月  8 17:55 file400
-r--r--r--  1 root root    0  3月  8 17:55 file444
-rw-rw-rw-  1 root root    0  3月  8 17:55 file666
-rwxrwxrwx  1 root root    0  3月  8 17:55 file777

 ファイルやディレクトリのオーナーはrootである。判りやすいようにディレクトリ名・ファイル名にパーミッションを数値化してつけて有る。

 これを、「piro791」アカウントから判定するとどうなるだろうか。
[piro791@kagami tmp]$ id
uid=500(piro791) gid=500(piro791) 所属グループ=500(piro791)

 このようなアカウントである。

 ファイルの読み取り権限についてチェックしてみる。事前の予想では、directory777、file444、file666、file777が真で、directory700とfile400が偽になるはずである。
[piro791@kagami tmp]$ test -r directory700;echo $?
1
[piro791@kagami tmp]$ test -r directory777;echo $?
0
[piro791@kagami tmp]$ test -r file400;echo $?
1
[piro791@kagami tmp]$ test -r file444;echo $?
0
[piro791@kagami tmp]$ test -r file666;echo $?
0
[piro791@kagami tmp]$ test -r file777;echo $?
0

 事前の予想通りの結果になった。特にエラーにもならず綺麗に判定されていることがわかる。
 では、「-w」オプションも試してみよう。すると…
[piro791@kagami tmp]$ test -w directory700;echo $?
1
[piro791@kagami tmp]$ test -w directory777;echo $?
0
[piro791@kagami tmp]$ test -w file400;echo $?
1
[piro791@kagami tmp]$ test -w file444;echo $?
1
[piro791@kagami tmp]$ test -w file666;echo $?
0
[piro791@kagami tmp]$ test -w file777;echo $?
0

 file444には読み取り権限こそあるものの、書き込みの権限は無いので、ここが「偽」となった。

 続いて「-x」も試してみよう。
[piro791@kagami tmp]$ test -x directory700;echo $?
1
[piro791@kagami tmp]$ test -x directory777;echo $?
0
[piro791@kagami tmp]$ test -x file400;echo $?
1
[piro791@kagami tmp]$ test -x file444;echo $?
1
[piro791@kagami tmp]$ test -x file666;echo $?
1
[piro791@kagami tmp]$ test -x file777;echo $?
0

 directory777にはcdコマンドで進入できるし、file777はバイナリーファイルかスクリプトファイルかは判らないが実行できる権限が付いていることが確認できる。

 では、ちょっとおかしなパーミッションを設定したファイルを用意してみた。
[piro791@kagami tmp]$ ls -l file771
-rwxrwx--x 1 root root 0  3月  8 18:15 file771

 ファイルのオーナーはrootで、これをpiro791アカウントで扱うとなると、読めない・書けない・実行できるという謎の状態になっている。(笑)
 testコマンドを試してみよう。
[piro791@kagami tmp]$ test -r file771;echo $?
1
[piro791@kagami tmp]$ test -w file771;echo $?
1
[piro791@kagami tmp]$ test -x file771;echo $?
0

 こんなおかしなファイルでもちゃんとパーミッションの判定はできるのであった。

サーバ管理者のためのプログラミング入門(シェルプログラミング基礎7~条件分岐その3) [サーバ管理者のプログラミング]

 数値の大小比較を行うコマンドtest」は、他にも文字列の比較も可能である。比較できる内容としては、ごく一般的な

1.2個の文字列が等しいかどうか/等しくないかどうか
2.与えられた文字列が「空」かどうか/「空ではない」かどうか

 というもの。

1.2個の文字列が等しいかどうか/等しくないかどうか

 文字列の比較も、数値の比較と同様に基本的な機能だと思うが、その方法を説明する。
 例を見てほしい。
#!/bin/sh
MOJI_A="ABCDEFG"
MOJI_B="ABCDEFG"

if test "$MOJI_A" = "$MOJI_B"
then
    # 2個の文字列は一致した
    echo "一致しました"
else
    # 2個の文字列は一致しなかった
    echo "一致しませんでした"
fi

 まるで面白みもないサンプルで申し訳ないが、これで要所は説明できるので説明したい。(笑)

 まず、重要なのは次の2点。
 ポイント1 文字列の比較には「=」記号を用いる
 ポイント2 比較する2個の文字列は必ずダブルクォートで囲むこと

 文字列が一致するかどうかの判定には、「=」記号を用いる。これは判りやすいと思う。ただ、うっかり数値の比較で用いたりしないことに注意しよう。なぜか?以下の例を見てもらいたい。(スクリプトを書くのが面倒くさかったのでコマンドを手入力している。:笑)
[root@server ~]# A="2"
[root@server ~]# B="+2"
[root@server ~]# test $A -eq $B
[root@server ~]# echo $?
0
[root@server ~]# test $A = $B
[root@server ~]# echo $?
1

 変数Aには「2」が代入されている。一方、変数Bには「+2」が代入されている。両方とも、数学的には同じ数値である。だから、testコマンドで-eqオプションを指定して比較している結果は0なので「真」…つまり両者は等しいと判定されている。一方、=オプションを指定して比較している結果は1なので「偽」…つまり両者は等しくないと判定されている。
 このような結果の違いを招くので、-eqと=とは正しく使い分けなければならない。

 次に、比較する2個の文字列は必ずダブルクォートで囲む必要が有る。いや、正確には囲むべきであるというべきかもしれない。その理由を説明しよう。

 実は、上記のサンプルではあえて囲まないで文字列の比較を実行しているが、エラーになっていないことが判るだろうか。これは、変数Aにも変数Bにも何らかの文字列が入っているから問題なく実行できているのである。
 では、以下の例を見てほしい。
[root@server ~]# A="ABC"
[root@server ~]# B=""
[root@server ~]# test $A = $B
-bash: test: ABC: unary operator expected

 変数Aには「ABC」という文字列が入っているが、変数Bには何も入っていない(正確にはヌル・ストリングを代入している)
 すると、testコマンドは変数が展開された状態では

test ABC =

 という状態でコマンドが実行された状態になってしまい、イコール記号の後ろにパラメータが不足している状態になってしまっている。このためエラーになってしまった。これを回避するために、ダブルクォートで囲む必要が有るのである。
[root@server ~]# A="ABC"
[root@server ~]# B=""
[root@server ~]# test "$A" = "$B"
[root@server ~]# echo $?
1

 このようにすると、変数Aと変数Bとの比較が実行され、等しくないという結果を得ることができている。変数が展開された状態を見ると一目瞭然。

test "ABC" = ""

 と、イコール記号の後ろに何らかのパラメータがあることがtestコマンドにちゃんと伝わるのである。
 文字列比較を行う場合には、100%必須というわけではないが、変数の中身がカラッポだった場合に備えてダブルクォートで囲んでおく癖をつけるべきである。

 なお、2個の文字列が一致しない場合に真と判定したい場合は、「=」記号の代わりに「!=」を用いる。
[root@server ~]# A="UNIX"
[root@server ~]# B="Linux"
[root@server ~]# test "$A" != "$B"
[root@server ~]# echo $?
0

 変数Aと変数Bとはそれぞれ異なる文字列が入っている。そして、その比較をしたtestコマンドの結果が0…つまり真になっている。


2.与えられた文字列が「空」かどうか/「空ではない」かどうか

 シェルスクリプトの中ではこちらもわりとよく用いられる条件判断になる。変数がカラッポなのか、カラッポでないか?というものである。

 まずは、変数がカラッポではない場合の条件判断を見てみよう。
#!/bin/sh

if test "$1"
then
        echo "パラメータは"$1"です。"
else
        echo "パラメータはありません。"
fi

 if構文・testコマンドの引数に注目。なんと、単にダブルクォートで囲んだ変数が記述されているだけになっている。これをシェルスクリプトとして実行するとこんな具合になる。
 まずはシェルスクリプトをそのまま引数無しで実行すると…
[root@kagami tmp]# ./string.sh
パラメータはありません。

 「パラメータはありません。」と表示された。つまり、testコマンドの実行結果は0でなかった(=つまり「偽」だった)ことを示している。
 では、何らかのパラメータをつけて実行してみるとどうか。
[root@kagami tmp]# ./string.sh ABC
パラメータはABCです。


 一方、変数の中身がカラッポだったら…という判断を行いたいケースでは、test -z "変数" という具合に、「-z」オプションを用いることになる。

サーバ管理者のためのプログラミング入門(シェルプログラミング基礎6~条件分岐その2) [サーバ管理者のプログラミング]

 シェルスクリプトで使う「if」構文は、コマンドが正常終了したか(≒終了ステータスが0だったかどうか)で真偽判断を行うということは理解してもらえただろうか。
 ここで一つの疑問…というか不満が出る人がいるかもしれない。

 一般的なプログラム言語では一般的な、大小比較とか出来ないんですかー?

 という問題。
 結論からいくと、シェルスクリプトの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で「>」を記述するとき、「&gt;」と書くからだ。同様に「<」なら「&lt;」だ。ちょうど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



 答えは下の方にある「続きを読む」からどうぞ。












続きを読む(練習問題の回答集)


サーバ管理者のためのプログラミング入門(シェルプログラミング基礎5~条件分岐) [サーバ管理者のプログラミング]

 最初に登場するのは、「条件分岐」。これがなくては何も始まらない。

 およそプログラミング言語たるもの、「もし、○○が××だったら、……をする。そうでなかったら~~をする。」という仕組みは必ず持っているものである。そして、シェルスクリプトだって例外なくそういう仕組みを持っているのである。ただし、世間一般のプログラミング言語がもっている条件判断の構文とちょっと違うので、そこだけ要注意。

 まずは書式から。
  if (コマンドA)
   then
    (コマンドB)
  fi


 最小の基本形としてはこんな感じ。
 まず、「コマンドA」が実行される。そのコマンドの実行結果(終了ステータス)が0(≒正常終了)だった場合に、「コマンドB」が実行されるのである。
 pingを使って実例を示そう。pingを投げてみて正常だったら、「ping OK」と表示させる簡単なもの。
#!/bin/sh

if ping -c 1 localhost
then
        echo "ping OK"
fi

 これを実行すると…
[root@kagami tmp]# ./ping_script.sh
PING kagami (127.0.0.1) 56(84) bytes of data.
64 bytes from kagami (127.0.0.1): icmp_seq=1 ttl=64 time=0.148 ms

--- kagami ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.148/0.148/0.148/0.000 ms
ping OK

 pingコマンドの結果も混じってしまって見づらいが、最後に「ping OK」と表示が出ていることがわかる。
 「localhost」だった箇所を「null.void」とかにするとどうなるか。
#!/bin/sh

if ping -c 1 null.void
then
        echo "ping OK"
fi

 これを実行すると、
[root@kagami tmp]# ./ping_script.sh
ping: unknown host null.void

 「ping OK」とは表示されなくなったことがわかる。

 では、pingが失敗したときに、「ping NG」と表示したい場合はどうしようか。つまり、「もし、コマンドが失敗したら…」という条件判断をしたい場合。
 そのような時には、ifとコマンドAとの間に「!」を置くとよい。つまり、

  if ! (コマンドA)
  then
    (コマンドB)
  fi


 という形にする。すると、コマンドAの終了ステータスが0でなかった場合にコマンドBが実行されるようになる。

 先ほどのスクリプトを修正する。なお、いちいち面倒くさいのでpingのに渡す引数はシェルスクリプトの「位置パラメータ」を渡すようにした。(笑)
[root@kagami tmp]# cat ping_script.sh
#!/bin/sh

if ! ping -c 1 $1
then
        echo "ping NG"
fi

 これで実行する場合は、「ping_script.sh localhost」みたいに実行するのである。
 やってみる。
[root@kagami tmp]# ./ping_script.sh localhost
PING kagami (127.0.0.1) 56(84) bytes of data.
64 bytes from kagami (127.0.0.1): icmp_seq=1 ttl=64 time=0.527 ms

--- kagami ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.527/0.527/0.527/0.000 ms
[root@kagami tmp]# ./ping_script.sh null.void
ping: unknown host null.void
ping NG

 このように、今度は「localhost」に対するpingではコマンドBに相当するechoが実行されていないが、「null.void」に対するpingではコマンドBに相当する部分が実行されていることがわかる。



 では、ping OKと、ping NGとを一緒に判断したい場合を紹介する。

  if (コマンドA)
  then
    (コマンドB)
  else
    (コマンドC)

  fi


 このように、「then」と「fi」との間に、「else」とコマンドCを追加する。すると、

    ・コマンドAの終了ステータスが0だった場合 … コマンドBが実行される
    ・コマンドAの終了ステータスが0でなかった場合 … コマンドCが実行される

 という挙動になる。pingの例で実例を示す。
#!/bin/sh

if ping -c 1 $1
then
        echo "ping OK"
else
        echo "ping NG"
fi

 このようなシェルスクリプトに修正する。pingが成功したときと失敗したときとでping OK、ping NGの表示が変わることを確認しよう。
[root@kagami tmp]# ./ping_script.sh localhost
PING kagami (127.0.0.1) 56(84) bytes of data.
64 bytes from kagami (127.0.0.1): icmp_seq=1 ttl=64 time=0.484 ms

--- kagami ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.484/0.484/0.484/0.000 ms
ping OK
[root@kagami tmp]# ./ping_script.sh null.void
ping: unknown host null.void
ping NG

 と、このようにOK、NGの表示が変化している事が確認できる。

 このように、コマンドAの実行結果に応じて、処理を分岐することができるのである。






 と、ここで平和裏に終わらせたいところではあるが、なにか釈然としない気持ちを持った人もいるだろう。(笑)そんな人には次のアーティクルを期待していただきたい。(笑)

サーバ管理者のためのプログラミング入門(シェルプログラミング基礎4~シェルスクリプト変数その2) [サーバ管理者のプログラミング]

 前アーティクルにおいて一通り変数の使い方を紹介したが、さらに重要な特殊な(?)変数がある。これらの変数はシェルスクリプトを作成してゆく中で重要な役割を担うので覚えておいてもらいたい。

その1:$?

 変数の名前に「?」というものがある。
 これは、「直前に実行されたコマンドの、終了ステータス」が獲得できる変数となっている。

 「終了ステータス」とはどういうことか。それは、要するにコマンドが正常終了したか否かを判断できるもの…と理解してもらってもまあ差し支えないと思う。

 実例を見てみよう。たとえば、pingコマンドで試す。pingが通った場合から見てみよう。
[root@kagami ~]# ping -c 1 localhost
PING kagami (127.0.0.1) 56(84) bytes of data.
64 bytes from kagami (127.0.0.1): icmp_seq=1 ttl=64 time=0.159 ms

--- kagami ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.159/0.159/0.159/0.000 ms
[root@kagami ~]# echo $?
0

 localhostにpingを投げている例。当然応答があるので正常な実行結果ということになる。
 pingコマンドの直後に$?の内容を確認すると「0」が入っていることが判る。
 ほとんど全てのコマンドは、「0」が入っていたときはコマンドは正常に終了したことを表している。

 続いて、機器の存在しないIPアドレスに対してpingコマンドを実行したらどうなるか。
[root@kagami ~]# ping -c 1 192.168.10.99
PING 192.168.10.99 (192.168.10.99) 56(84) bytes of data.

--- 192.168.10.99 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms

[root@kagami ~]# echo $?
1

 今度は「1」が入っていた。
 コマンドとしては異常終了していることを意味している。

 続いて、hostsやDNSで名前解決できない場合はというと…
[root@kagami ~]# ping null.void
ping: unknown host null.void
[root@kagami ~]# echo $?
2

 今度は「2」になっている。
 pingコマンドは、異常終了した場合「終了ステータス」に異常だった理由を入れている。まあ、あまり深く気にすることはそう多くないと思うが。

 というわけで、「$?」が0だったか、0じゃなかったか…によって、コマンドが正常に終了したか、異常終了したかが判断できるのであった。



その2:$!

 今度は、変数名「!」について。
 これは「直前に実行したバックグラウンドプロセスのPID」を意味する。

 これも実例を見てみよう。
 sleepコマンドをバックグラウンドで実行して、そのPIDを調べてみると…
[root@kagami ~]# sleep 60000 &
[1] 4485
[root@kagami ~]# echo $!
4485

 このように、バックグラウンドで実行されているであろうsleepコマンドのPIDが表示され、一致している。
 本当に一致しているか、psコマンドで確認してみると…
[root@kagami ~]# ps -ef | fgrep "4485"
root      4485  1390  0 17:31 pts/0    00:00:00 sleep 60000
root      4487  1390  0 17:31 pts/0    00:00:00 fgrep 4485

 fgrepコマンドも一緒に引っかかっているが、確かにsleepコマンドのPIDそのものである。

 killコマンドでsleepコマンドを強制終了させてみる。
[root@kagami ~]# kill 4485
[root@kagami ~]#
[1]+  終了しました      sleep 60000
[root@kagami ~]# ps -ef | fgrep "4485"
root      4489  1390  0 17:31 pts/0    00:00:00 fgrep 4485

 間違いなくsleepコマンドが消えてなくなった。

 この変数はバックグラウンドでコマンドを実行中に別のことを実行するなどする際に役立つ。



その3:$$

 続いて変数名「$」。これは、「現在実行中のコマンドラインインタプリタ…つまりshとかbashとか…のPID」を意味する変数になる。

 これも実例から見てみよう。
[root@kagami ~]# echo $$
1390
[root@kagami ~]# ps -ef | fgrep 1390
root      1390  1386  0 Jan18 pts/0    00:00:00 -bash
root      4496  1390  0 17:38 pts/0    00:00:00 ps -ef
root      4497  1390  0 17:38 pts/0    00:00:00 fgrep 1390

 子プロセスも表示されているが、変数「$$」に入っている数値1390は、bashのPIDと確かに一致していることがわかる。
 では、現在のbashから、さらに子プロセスとしてbashを実行するとどうか。
[root@kagami ~]# bash -c 'echo $$;ps -ef | fgrep $$'
4501
root      4501  1390  0 17:40 pts/0    00:00:00 bash -c echo $$;ps -ef | fgrep $$
root      4502  4501  0 17:40 pts/0    00:00:00 ps -ef

 先ほどと同じターミナルで実行しているが、別の数値が表示されている。同時に実行したpsコマンドにも4501というPIDでbashが表示されている。

 PIDが変化してしまった?という疑いを晴らすため、再度確認してみるが…
[root@kagami ~]# echo $$
1390
[root@kagami ~]# ps -ef | fgrep 1390
root      1390  1386  0 Jan18 pts/0    00:00:00 -bash
root      4506  1390  0 17:42 pts/0    00:00:00 ps -ef
root      4507  1390  0 17:42 pts/0    00:00:00 fgrep 1390

 やはり同じPIDで変化していないことが確認できた。



その4:位置パラメータ

 今度は、「位置パラメータ」の出番。
 「$」記号に続けて数値を記述すると、それは「位置パラメータ」を示す変数となる。
 「$1」は一番目のパラメータ、「$2」は二番目のパラメータ…「$3」なら三番目のパラメータ…という具合。

 「位置パラメータ」とはそもそも何?という人もいるだろう。簡単に言うと、コマンドに続けて記述しているオプションや引数のことである。たとえば、「ls -la /tmp」というコマンドを例にとると、
  ・最初の「ls」…コマンド名 (実は、「$0」という変数にこの名前が入っている)
  ・オプション「-la」…第1パラメータ → これは「$1」に入っている
  ・引数「/tmp」…第2パラメータ → これは「$2」に入っている
 こんな具合だ。簡単なシェルスクリプトで実際に試してみよう。

 まず、以下のようなシェルスクリプトを用意してみた。
#!/bin/sh

echo 0","$0
echo 1","$1
echo 2","$2
echo 3","$3

 chmod +xして、とりあえず実行してみよう。すると…
[root@kagami ~]# /tmp/param_test.sh
0,/tmp/param_test.sh
1,
2,
3,

 こんな具合に。

 では、適当にパラメータとか記述してみるとどうなるだろか。
[root@kagami ~]# /tmp/param_test.sh konata miyuki tsukasa
0,/tmp/param_test.sh
1,konata
2,miyuki
3,tsukasa

 パラメータを3個並べてみると、それぞれ1~3にそれぞれの文字列が入っていることが判る。これが「位置パラメータ」である。(C/C++的表現では「argv」に相当する)


 で、この「位置パラメータ」が何個列挙されているか知りたいこともあるだろう。そのような情報を格納するのが変数「$#」である。

 先ほどのシェルスクリプトを以下のように改造してみよう。
#!/bin/sh

echo "#,"$#
echo 0","$0
echo 1","$1
echo 2","$2
echo 3","$3

 これを先ほどと同じように実行すると…
[root@kagami ~]# /tmp/param_test.sh
#,0
0,/tmp/param_test.sh
1,
2,
3,
[root@kagami ~]# /tmp/param_test.sh konata miyuki tsukasa
#,3
0,/tmp/param_test.sh
1,konata
2,miyuki
3,tsukasa

 引数を何も書かずに実行すると、変数「$#」には0が。3個つけて実行すると変数「$#」には3が入っている。これによって、「位置パラメータの個数」を知ることが可能となるのである。(C/C++的表現では「argc」に相当している)

 また、「位置パラメータ」を分割せずにそのままの形で欲しい!という時もある。そのような時には変数「$*」を用いると良い。
 先ほどのシェルスクリプトをさらに改造する。
#!/bin/sh

echo "#,"$#
echo 0","$0
echo 1","$1
echo 2","$2
echo 3","$3

echo "*,"$*

 これを再度同じように実行すると…
[root@kagami ~]# /tmp/param_test.sh
#,0
0,/tmp/param_test.sh
1,
2,
3,
*,
[root@kagami ~]# /tmp/param_test.sh konata miyuki tsukasa
#,3
0,/tmp/param_test.sh
1,konata
2,miyuki
3,tsukasa
*,konata miyuki tsukasa

 …という具合になる。

 ひとまず覚えておきたい基本的な変数はこんなところかな。

 次はいよいよプログラミングっぽい感じのすることをやってみることとする。

サーバ管理者のためのプログラミング入門(シェルプログラミング基礎3~シェルスクリプト変数その1) [サーバ管理者のプログラミング]

 続いて、シェルスクリプトプログラミングにおいて…いや、ありとあらゆるプログラミングにおいて欠かせない「変数」について説明しよう。

 「変数」とは、何らかの数値や文字列を一時的に格納する領域…と覚えてもらいたい。要するに、1とか2とか3とか…というような数値データ、ユーザー名とかサーバ名とかディレクトリ名とかの文字の羅列というような文字列データなどを一時的に格納することができる。

 「変数」がどのような局面で役に立つか。判りやすい例でいえば、「処理内容は全く同じだが、処理対象となるサーバが違う」とかそんなケース。以前、「yum update」をいろんなサーバで実行する例を見てもらったが、まさにあんな処理を行いたい場合などに「変数」が役に立つ。
 あるいは、あるファイルを処理の対象にすると仮定する。シェルスクリプトのあちこちでそのファイルを使用したいなんてケースも、「変数」が役に立つ。というのも、ファイル名をシェルスクリプトのあっちこっちで書き並べておくと、「ファイル名(ファイルの置いてある場所)が変更になりました…」なんてケースに泣きを見ることがあるのである。(エディタの置換機能で一斉に置き換えればいいじゃん!…なんていう人もいるかもしれない。が、ファイルが1個ならそれでも良いが、2個3個…となってくるとリスクが飛躍的に大きくなる)

 それでは、変数の使い方についてざっと説明しておこう。

 実は、コマンドラインインタプリタ(shとかbashとかkshとかcshとか…)は日常的に「変数」を活用している。もしかしたらすでにその「変数」に触れている可能性も高い。

 ためしに、プロンプトから、「set」コマンドを実行してみると、こんなような表示が出ると思う。

[root@kagami ~]# set
BASH=/bin/bash
BASH_ARGC=()
BASH_ARGV=()
BASH_LINENO=()
BASH_SOURCE=()
BASH_VERSINFO=([0]="3" [1]="2" [2]="25" [3]="1" [4]="release" [5]="i686-redhat-linux-gnu")
BASH_VERSION='3.2.25(1)-release'
COLORS=/etc/DIR_COLORS.xterm
COLUMNS=90
DIRSTACK=()
 (途中省略)
TERM=xterm
UID=0
USER=root
_=USERNAME
consoletype=pty


 これは、bashで実行した結果。表示内容としては、「ナントカ=カントカ」というならびになっている。(一部、配列変数を使用しているため風変わりな表示内容もあるが、そこはとりあえず読み飛ばしてもらいたい)
 たとえば、「BASH=/bin/bash」という内容(おそらく1行目)は、
 変数「BASHの内容は、/bin/bash」ですよ
 という内容になっている。

 サーバ管理者としておそらく身近な変数の一つに、「PATH」が挙げられる。この変数はコマンドを入力した際に、そのコマンドが置かれているディレクトリを検索する対象を列挙している。

[root@kagami ~]# set | grep PATH
PATH=/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin


 単にコマンド名が入力された場合(たとえば「ls」とか)/usr/binとか/usr/sbinとかその辺のディレクトリを探してコマンドを実行するに必要なファイルを探し出す場所をこの変数は保持している。

 今、変数「PATH」の内容を確認するためにsetコマンドとgrepコマンドを使用したが、変数の内容を確認する正しい方法があるので、まずはそれを紹介する。

1.変数の参照方法

 シェル変数を参照するには、「$」記号に続けて変数名を記述することで、その変数を参照できる。ここでいう「参照」とは、変数の中身をいきなり表示するという意味ではないことに注意が必要。
 では、先ほど表示してみた変数「PATH」の内容を参照・表示してみるにはどうすればよいか。「$」記号に続けて変数名を記述する…ということなので、変数PATHを参照する際には「$PATH」という指定をすればよいことになる。ただ、これをこのままコマンドラインから入力しても表示されるわけではない。ここでは、「echo」コマンドと組み合わせることで表示することになる。

[root@kagami ~]# echo $PATH
/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin


 変数PATHの中身が表示された。setコマンドでの実行結果と微妙に異なるが、setコマンドは「変数名=内容」という書式で、全ての環境変数が表示されていた。こちらは指定した変数の内容「だけ」が表示されている。

 なお、変数名を記述する際、「{」と「}」とで囲むケースがある。詳しくは別途解説するが、「$PATH」と指定するところを「${PATH}」と記述してもよいし、またそのように指定しなければならなくなるケースも存在することを覚えておいて欲しい。


2.変数に値を代入(セット)する

 変数の内容を参照できるようになったところで、今度は変数に何らかの値・情報を代入(セット)してみよう。
 変数への代入方法は、中学校・高校の数学の授業で散々見た(はずの)記述方法と同じ。「変数=値」という記述方法をとる。
 それでは、ここでは新たに変数「TEST」に「1」を代入してみる。その記述方法は、「TEST=1」である。
なお、ここで重要な注意事項がある。変数名とイコール、イコールと値の間には空白(スペースやタブ)を入れないこと。空白を入れずに続けざまに記述しなければならない。
 では、実際に試してみよう。

[root@kagami ~]# echo $TEST

[root@kagami ~]# TEST=1
[root@kagami ~]# echo $TEST
1


 代入する前に、「echo $TEST」を実行してみた。何も表示されていないが、これはまだ変数「TEST」に何も代入されていないことを示している。
 そして変数「TEST」に1を代入。続けて再度変数の内容を参照してみると、ちゃんと「1」が表示され、変数「TEST」に値が代入されていることがわかる。

 なお、変数に代入できるデータはなにも数値とは限らない。文字列を格納することもできる。

[root@kagami ~]# TEST=hogehoge
[root@kagami ~]# echo $TEST
hogehoge


 変数「TEST」に、「hogehoge」という文字列を代入した例。ちゃんと文字列が格納されていることが確認できる。

 変数の内容をカラッポにしたいというケースもたまに出てくる。そのような場合は「""」(ダブルクォーテーションを2個続けたもの)を代入することが一般的だ。「TEST=""」という具合に。なお、「''」(シングルクォーテーションを2個続けたもの)でもよい。

3.変数そのものを削除したい。

 それほど多くは無いかもしれないが、処理の内容・方法によっては「変数そのものを削除したい」ということもあるかもしれないので、その方法も紹介しておく。

 「unset」コマンドを用いると、その変数を削除することができる。
 先ほど紹介した、「TEST=""」の場合、変数の中身がカラッポになるだけで、変数そのものは残る。

[root@kagami ~]# TEST=""
[root@kagami ~]# set | grep TEST
TEST=


 unsetコマンドを用いた場合は、変数そのものが無くなる。

[root@kagami ~]# unset TEST
[root@kagami ~]# set | grep TEST
_=TEST


 「_=TEST」とか表示はあるものの、「TEST=ナントカ」という表示が消えていることが判る。これは変数「TEST」そのものが存在していないことを意味している。
 unsetコマンドで変数名を指定する際は「$」記号は不要である。
前の10件 | - サーバ管理者のプログラミング ブログトップ
メッセージを送る