12. シェルスクリプトで yes or no

12.1. シェルスクリプトの中で対話的に処理をしたい

  • シェルスクリプトを書いている時、実行している人に複数ある処理のうちの1つを選んでもらいたい、なんて事がよくありませんか? 例えば、

    * yes だったら、あるファイルを削除
    * no だったら、何もしない
    

    のような時、どのように書けば良いのでしょうか?

12.2. readコマンド で対話的に変数を代入する

  • 対話処理をするにはreadコマンドが一般的です。LinuxOS等にログインしシェルに打ち込んでみましょう。

    $ read MAGICA
    ※ $はプロンプトの意味なので打たないでね!
    

    readコマンドを打ち込みEnterを押すと、プロンプトが返ってきません。ここで好きな文字列を入力します。例えば、

    Mami
    

    と入力してみます。これで MAGICA という変数に Mami という文字列が代入されました。

    本当に代入されたのかどうか確認してみましょう。変数名の頭に $ を付けてechoコマンドで確認します。

    $ echo $MAGICA$はプロンプトの意味なので打たないでね!
    

    次のように表示されれば成功です。

    Mami
    

12.3. 参照する変数によって処理内容を変更する case文

  • シェルスクリプトに限らず、条件によって処理を分岐するには if文 が有名かつ汎用性がありますが、今回はスマートな書き方として case文 を使います。これもまたシェルに打ち込んでみましょう。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    $ case $MAGICA in
          Mami)
              echo 'last name of Mami is Tomoe.'
              ;;
          *)
              echo 'MAGICA is none.'
              ;;
      esac$はプロンプトの意味なので打たないでね!
    
    • 前のセクションから続けているならば「last name of Mami is Tomoe.」と表示されたはずです。
    • 「 ) 」の左側に変数に代入される可能性のある文字列を書きます。上から順番に評価され、マッチした文字列があった場合は、その項目の処理を実行し終了します。次の項目には進みません。
    • 一番最後に、どんな文字列にもマッチする 「 * 」 という項目を作っておくのが定石です。

12.4. readコマンドとcase文を組み合わせてスクリプトを書く

  • では、実際に yesと打ち込むと”tyeped yes.”、noと打ち込むと”tyeped no.”と出力するようなスクリプトを書いてみましょう。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    #!/bin/sh
    
    echo "Type yes or no."
    
    read answer
    
    case $answer in
        yes)
            echo -e "tyeped yes.\n"
            ;;
        no)
            echo -e "tyeped no.\n"
            ;;
        *)
            echo -e "cannot understand $answer.\n"
            ;;
    esac
    
    • answerという変数にyesかnoを代入させて、yes、no、それ以外の文字列が入力された場合で出力される文章がそれぞれ異なるスクリプトが出来上がりました!

12.5. yesかnoの場合はいいけど、それ以外の時は?

  • でもちょっと待って下さい。yesかno以外の文字列が入力された場合、yesかnoのどちらかが入力されるまで処理を繰り返したくありませんか? 繰り返し処理をさせたい時は、while文を使います。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    #!/bin/sh
    
    while true;do
    
        echo "Type yes or no."
    
        read answer
    
        case $answer in
            yes)
                echo -e "tyeped yes.\n"
                break
                ;;
            no)
                echo -e "tyeped no.\n"
                break
                ;;
            *)
                echo -e "cannot understand $answer.\n"
                ;;
        esac
    done
    
    • while true と書くとwhile文の中を常にループ(繰り返し処理)させる事ができます。
    • もちろん、永久ループするのは困るので、yesかnoを打った時の処理に break を書くことによりループを抜けられるようにします。
    • yesでもnoでも無い時は、”cannot understand 「打ち込んだ文字列」”と表示させ、while文の頭に戻って処理が繰り返されます。

12.6. 関数化する

  • 実用的に使う為には関数化します。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    #!/bin/sh
    
    yes_or_no_while(){
        while true;do
            echo
            echo "Type yes or no."
            read answer
            case $answer in
                yes)
                    echo -e "tyeped yes.\n"
                    return 0
                    ;;
                no)
                    echo -e "tyeped no.\n"
                    return 1
                    ;;
                *)
                    echo -e "cannot understand $answer.\n"
                    ;;
            esac
    done
    }
    
    yes_or_no_while
    
    • シェルスクリプトでは関数を作り、コマンドのように実行する事ができます。今回は yes_or_no_while という関数を作り実行しています。
    • 前のセクションでは while文 を抜けるのに break を使っていましたが、関数化した際には return を使い関数を終了させています。関数化している場合は break より関数の終了値を返せる return がお勧めです。 ちなみに今回は終了値をyesの場合は0、noの場合は1と返していますが、このスクリプトだけでは特に意味がありません。

12.7. 再帰呼出し

  • さて無事にyesとno以外の文字列が入力された場合は、繰り返しyesかnoを聞くスクリプトが出来上がりました。が、while文はネスト(入れ子構造)になってしまい若干見辛いかなあ、という時があります。

  • そう、もっとスマートに書く方法があります。関数の中で、同じ関数を呼び出す 再帰呼出し という方法です。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    #!/bin/sh
    
    yes_or_no_recursive(){
        echo
        echo "Type yes or no."
            read answer
            case $answer in
                yes)
                    echo -e "tyeped yes.\n"
                    return 0
                    ;;
                no)
                    echo -e "tyeped no.\n"
                    return 1
                    ;;
                *)
                    echo -e "cannot understand $answer.\n"
                    yes_or_no_recursive
                    ;;
            esac
    }
    
    yes_or_no_recursive
    
    • case文 の一番最後の項目に注意して下さい。 yes、no以外の文字列の場合「 * 」は”cannot understand $answer.”と出力し、18行目で yes_or_no_recursive という自分自身を呼び出しています。これでyes、no以外の時は関数の初めから繰り返し実行されます。ネストが減ってなんとなくスマートになった気がしませんか?

12.8. bashで書いてみよう

  • 上で書いていたスクリプトは汎用性を高くする為に、sh(Bourne Shell)で書いていましたが、bashだともう少しかっこいい書き方ができます。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    #!/bin/bash
    
    function yes_or_no_select(){
        PS3="Answer? "
        while true;do
            echo "Type 1 or 2."
            select answer in yes no;do
                case $answer in
                    yes)
                        echo -e "tyeped yes.\n"
                        return 0
                        ;;
                    no)
                        echo -e "tyeped no.\n"
                        return 1
                        ;;
                    *)
                        echo -e "cannot understand your answer.\n"
                        ;;
                esac
            done
        done
    }
    
    yes_or_no_select
    
    • スクリプトを見るだけではわかりませんが select文 を使うと表示がかっこよくなります。
    • bashでは関数を書くときに function と頭に明示する事ができます。つけなくても関数は書けますが明示したほうがスクリプトが見易くなります。

12.9. まとめ

  • readコマンド で対話的に変数を代入する
  • case文 で分岐処理
  • while文 でループ(繰り返し処理)
  • 再帰呼出し でスマートにループ