意外に知らない人が多いシェルスクリプトについて、基本的な部分の解説。主に初心者をターゲットとした内容。

まれに 「シェルスクリプト」を単に「シェル」と呼ぶ人がいるが、それは厳密には間違いである。「シェル」と「シェルスクリプト」は、無関係ではないが厳密には別物である。

当ページ 管理人のネット上・実社会での観測結果によると、「シェルスクリプト」を「シェル」と呼ぶ人のスキルは著しく低い傾向がある。

同感である。

当サイトでは bash を用いてシェルスクリプトを作成している。まずは簡単なシェルスクリプトを作成してみる。vi で helloworld.sh という名前で新規ファイルを作成し、以下の内容を入力する。

#!/bin/bashecho "Hello World !"exit 0

先頭に指定した #! で始まる「シバンこれは決まり文句のようなものなので、必ず 1 行目に指定すること

さっそく helloworld.sh を実行してみる。シェルスクリプトを実行する方法は大きく分けて 2 つある。

  1. bash コマンドに実行するシェルスクリプトのファイルを指定する。e.g. $ bash helloworld.sh
  2. シェルスクリプトのファイルをコマンドとして直接実行する。e.g. $ ./helloworld.sh

シェルスクリプトを実行するにはこれが一番簡単な方法だと思う。bash コマンドに引数として実行するシェルスクリプトのファイル名を指定するだけ。$ cd /tmp$ bash helloworld.shbash: helloworld.sh: そのようなファイルやディレクトリはありません$ bash /home/sunone/helloworld.shHello World !

シェルスクリプトを (ls や cat のように) コマンドとして直接実行するためには、主に以下の 2 つの条件が必要。

  1. ファイルのパーミッションに実行権が付与されていること
  2. シェルがファイルの場所を特定できること

まずはじめにファイルの実行権を確認してみる。実行権にあたる x がないので、chmod コマンドでこれを付与する。これで前述の 1 (ファイルのパーミッションに実行権が付与されていること) の条件を満たしたので、次は 2 (シェルがファイルの場所を特定できること) を満たす条件を考える。$ helloworld.shbash: helloworld.sh: コマンドが見つかりません

シェルは実行したコマンドが、カレントディレクトリ内にあったとしても、それを実行してはくれない。ls や chmod がコマンド名だけで実行できるのは、環境変数 PATH にこれらコマンドが存在する /bin が設定されているためである。
実際に echo コマンドで環境変数 PATH を確認してみる。
$ echo $PATH/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/usr/local/bin:/usr/bin

helloworld.sh を実行するには、環境変数 PATH にカレントディレクトリ(.)を設定してもよいが、一般にカレントディレクトリを環境変数 PATH に設定するのは セキュリティ上好ましくないとされている (開発で使用している仮想環境、等であればそこまで気にする必要はないであろうが)。さらに export コマンドで、現在ログイン中のシェルと、そこから派生したシェルで、指定した変数 (PATH) の値を有効にすることができる。$ helloworld.shHello World !

シェルスクリプトは他のプログラミング言語と違い、 if や for といった制御文もコマンドであり、それらを含めてすべてコマンドの羅列にすぎない。シェルスクリプトの場合は、 シンタックスエラーをコマンドエラーとして考えるとわかりやすい$ var = "hoge"bash: var: コマンドが見つかりません

これはシェルが変数 var を「スペースがあることによって var コマンドと判断」し、それを実行しようとしたために起きたエラーである。そのため、変数に値を設定するには、= の前後にスペースがあってはいけないのだ。 筆者がシェルスクリプトを作成するときに意識しているポイントをまとめてみる。

  1. シェルスクリプトはすべてコマンドの羅列である
  2. シンタックスエラーではなくコマンドエラーとして考える
  3. シェルスクリプトはコマンドライン・テンプレートである
  4. 最終的にどのようなコマンドが実行されるかイメージする

1、2 に関しては前述のとおり。3、4 を理解できるようになると、初心者レベルを脱却できるはずだ。シェルスクリプトはコマンドラインのテンプレートして捉え、最終的にどのようなコマンドが実行されるかをイメージして作成するとレベルアップの近道になるはずだ

# シャープから行末はコメントになります。# if文の使用例if [ 0 -eq 0 ]; then  echo "equal."fi

条件式には [ コマンドを使用します。これは if と同様に文法の一部ではなく /bin/[ というコマンドである。したがって [ の前後は必ずスペースが必要になる。それに続いている 0-eq もパラメータであるため、区切りとしてスペースが必要になる。if 文での複数分岐も可能。

# read コマンドでキーボードから入力した文字列を、変数 str に設定するread str# 変数は必ず "" で囲んで使用する (変数に値が入っていない場合、"" がないと文法エラーになるため)if [ "$str" = "hoge" ]; then  echo "hoge"elif [ "$str" = "fuga" ]; then  echo "fuga"else  echo "unknown"fi

elif を増やして更に分岐を加えることも可能であるが、そういった場合は次の case 文を使用した方がよいだろう。

# 各分岐の最後の ;; を忘れずにcase "$str" in  "hoge" ) echo "hoge"           echo "hoge" ;;  "fuga" ) echo "" ;;  * ) echo "unknown" ;;esac

パターンの部分に正規表現は使用できないが、代わりにワイルドカード (すべての文字列と一致の「*」と、任意の一文字と一致の「?」) が使用可能。

次はループ処理を行う for 文。動作的には他の言語にある foreach 文と同様である。

# in に続く文字が次々に変数 i に代入されるfor i in 0 1 2 3do  echo $idone

while 文のループ継続条件式には、if 文と同様に [ コマンドを使用可能である。

while [ "$str" = "" ]do  read strdone

無限ループにするには : コマンド(ヌルコマンド)を指定する。: コマンドは一切の処理を行わずに、ただ正常終了するだけのコマンドである

while :do  read str  if [ "$str" = "end" ]; then    # break コマンドでループを抜ける    break  fidone

このように while 文や if 文の条件式部分には、[ コマンド以外の任意のコマンドが指定可能である。それは while 文や if 文が見ているのは条件式自体ではなく、「終了ステータス」というコマンドの実行結果を表す数値だからである。

# hogefile の中に文字列 hoge があるか?if grep 'hoge' hogefile >/dev/null 2>&1  echo "hoge found."fi

>/dev/null 2>&1 を指定すると、コマンドの実行結果やエラーなどの出力をディスプレイに一切表示しないようになる (ちなみに grep コマンドの -sq オプションでも同様の効果を得られる)。
>/dev/null2>&1 は逆にしてはいけない。必ずこの順番で指定すること。
ちなみに「1」は標準出力、「2」はエラー出力を意味している。要するに >/dev/null 2>&1 は、「2 (エラー出力)」を「1 (標準出力)」にまとめて、さらに出力先を「/dev/null (ごみ箱)」に変更する、という意味になる。

「終了ステータス」は、コマンド実行終了後に変数 $? へ自動的に設定されている。通常、コマンド実行成功の場合は「0」、コマンド実行失敗は「0 以外」となる。$ touch hoge$ ls hogehoge$ echo $?0

直前に hoge ファイルを作成しているので、当然 ls コマンドは成功し、終了ステータスは 0 になる。$ rm hoge$ ls hogels: hoge: No such file or directory$ echo $?2

今度は hoge ファイルが存在しないため、ls コマンドの終了ステータスは 2 (0 以外)となっている。