Tomitomi's blog

勉強したこと、やってみたことなどを書き連ねていく場

シェルスクリプト入門 ~数あてゲーム~

はじめに

シェルスクリプトは、シェル操作を自動化することに特化したスクリプト言語です。 特定用途に特化しているとは言え、変数やif文、for文やwhile文、関数などが使えるため、 手続き型プログラミング言語の一種と見ることもできます。 そこで、本稿では、他のプログラミング言語の入門書のように、 数あてゲームを作りながら、シェルスクリプトの基本的な要素や制御構造の導入をしようと思います。

数あてゲームは、プログラミング言語の入門で書くプログラムとしては Hello worldの次に有名(と勝手に思っている)なプログラムです。 最終的にできるプログラムは以下のような仕様になります。

  1. 0~99のランダムな数を正解として保持しておく。
  2. ユーザに数を入力してもらう。
  3. ユーザが入力した数が正解なら"Correct"と表示して終了
  4. ユーザが入力した数が正解より大きければ"Too big!"と表示、小さければ"Too small!"と表示して2に戻る。

この記事では、シェルとしてbashを対象にしています。 他のシェルでは書き方が違う場合がありますのでご了承ください。

入力して出力するだけのスクリプト

#!/bin/bash

echo "Input your answer."
read answer

echo "Your answer is $answer"

まずは、ユーザーからの入力を受け付ける部分から作ります。 このスクリプトは、readコマンドでユーザーからの入力をanswerに格納し、その値を出力しています。

ここでは変数について説明します。

シェルスクリプトの変数は代入するときと、値を使用するときで書き方が違います。 下に基本的な書き方を紹介します。

# 値を代入するとき
hoge="Hello"

# 値を使用するとき
echo $hoge

値を代入するときは変数名単体で書き、値を使用するときは、変数名の前に$を付けます。

変数の値を使用するとき、その場所に変数の値が展開されます。 例えば、echo $hogeの部分では$hogeの部分が変数hogeの値で置き換えられて、 echo Helloというコマンドが実行されます。 このような置き換えを展開と呼び、変数の展開以外に、算術式展開、コマンド置換、プロセス置換などがあります。

乱数を生成して特定の範囲に収める

#!/bin/bash

secret_number=$(( $RANDOM % 100 ))

echo "The secret number is $secret_number"

echo "Input your answer."
read answer

echo "Your answer is $answer"

次に、正解の値をランダムに生成する処理を作ります。 生成しただけだとどんな値が生成されたのかわからないので正解の値を確認用に出力しています。 ここでは、先ほどのスクリプトに加えて、0~99までの間の乱数を生成して変数secret_numberに代入する処理が追加されています。 RANDOMという変数は後述するシェル変数の一種で、0~32767のランダムな整数に展開される特殊な変数です。 0~32767の範囲の乱数を0~99の範囲に収めるため、プログラム中では、100の剰余が計算されています。

ここでは特殊な値と算術式展開について説明します。

このようにシェルスクリプトには、予約されている特殊な値がいくつかあります。 大きく分けると位置パラメータ、特殊パラメータ、シェル変数の3種類です。 位置パラメータはいわゆる引数で、先頭から順に$1$2$3...と書きます。 特殊パラメータは読み込み専用の特別なパラメータで、引数の個数を表す$#や直前のコマンドの終了ステータスを表す$?などがあります。 シェル変数はシェルがある種の情報を提供したり、シェルの設定をしたりするときに用いる変数です。 予約されている値は、bashのマニュアルに載っています。*1

シェルスクリプトでの演算は、算術式展開によって行います。 算術式展開の書き方は以下の通りです。

$(( 算術式 ))

これは、算術式の計算結果に展開されます。

入力を正解と比較して分岐する。

#!/bin/bash

secret_number=$(( $RANDOM % 100 ))

echo "The secret number is $secret_number"

echo "Input your answer."
read answer

if [ "$answer" -gt "$secret_number" ]; then
  echo "Too big!"
elif [ "$answer" -lt "$secret_number" ]; then
  echo "Too small!"
else
  echo "Correct"
fi

次に、ユーザーが入力した値が正解かどうか判別する処理が必要ですね。 このスクリプトは、ユーザの入力とと変数secret_numberを比較して、 より大きければToo big!、より小さければToo small!、正しければCorrectと表示する処理が追加されています。

ここでは、if文とtestコマンドについて説明します。

シェルスクリプトのif文は以下のような構文になっています。

if コマンド; then
  コマンドが成功した場合の処理
else
  コマンドが失敗した場合の処理
fi

ここでいう、コマンドの成功とは、コマンドの戻り値(C言語で言うとmain関数のreturnで返された値)が0である場合で、 それ以外の場合は失敗ということになります。

if文で、大小比較や値の一致などの条件式の処理を行うためには、testコマンドを使用します。 シェルスクリプトそれ自体では、大小比較や値の一致などの判断を行っていません。 シェルスクリプトのif文は、コマンドを実行して、それが成功したか、失敗したかによって分岐しているだけです。 大小比較や値の一致など、他の多くのプログラミング言語のif文の中でやっているような、 条件式の処理は、それを行う専用のコマンドで行うことになります。 その条件式の処理を行うコマンドがtestコマンドです。

testコマンドを用いることで、シェルスクリプトのif文で条件式を用いた条件分岐が可能になります。 testコマンドは、引数として値や比較演算子等を受け付け、 それらを条件式とみなして真偽を判定します。 真の場合はコマンドは成功し、偽の場合は失敗します。 比較演算子等の詳細は、testコマンドのマニュアル*2をご覧ください。 例えば、A>Bという式をtestコマンドで表現すると以下のようになります。

test $A -gt $B

この場合、AがBより大きいとコマンドは成功し、等しいか小さいとコマンドは失敗します。 これを以下のようにif文に乗せると、変数の値の大小比較による条件分岐が実現できます。

if test $A -gt $B ;then
  echo "A is greater than B!!"
fi

また、testコマンドには、別名として[というコマンドがあります。 これは、最後に]を要求してくることを除いてはtestコマンドと同じふるまいをするコマンドです。 これを用いることで、数あてゲームのプログラムと同様に以下のようにif文を記述できます。

if [ $A -gt $B ]; then
  echo "A is greater than B!!"
fi

正解を入力するまで繰り返す。

#!/bin/bash

secret_number=$(( $RANDOM % 100 ))

loop=0

while [ "$loop" -eq 0 ]; do
  echo "Input your answer."
  read answer

  if [ "$answer" -gt "$secret_number" ]; then
    echo "Too big!"
  elif [ "$answer" -lt "$secret_number" ]; then
    echo "Too small!"
  else
    echo "Correct"
    loop=1
  fi
done

最後に、繰り返し処理を追加して、正解を入力するまでスクリプトの実行が続くようにしました。 もう、正解の確認は要らないので、そのための行は削除してあります。

ここでは、while文について説明します。 while文の基本的な構文は以下の通りです。

while コマンド; do
  処理
done

コマンドが成功している限り処理を繰り返します。 これも、if文と同様に、testコマンドを使って以下のように書けます。

while [ 条件式 ]; do
  処理
done