シェルスクリプト
シェルにより入力するコマンドは、シェルスクリプトにより順次実行可能な形式になります。そ
れを応用することにより、コマンド入力による作業を自動化することが可能です。その際に必要な
技術である、シェルスクリプトを学習しましょう。
【内容】
1 シェルとシェルスクリプト
2 プログラミング
2.1 プログラムの例
2.2 プログラムの要素
3 シェルスクリプトの作成
3.1 シェルスクリプトの作成
3.2 変数
3.3 echo コマンド
3.4 read コマンド
3.5 シェル変数
3.6 環境変数
3.7 コメント
3.8 引用符
3.9 引数
3.10 shift 文
3.11 エスケープシーケンス
3.12 source コマンド
3.13 バックスラッシュ
4 条件分岐
4.1 if 文
4.2 ファイルの属性比較
4.3 複数の条件を重ねる
4.4 一対多の条件分岐
5 繰り返し
5.1 for 文
5.2 while/until 文
5.3 select 文
5.4 繰り返しの制御
6 サブルーチン
6.1 関数
6.2 return 文
7 実際のシェルスクリプト
7.1 起動スクリプト
7.2 関数のシェルスクリプト
8 デバッグ
8.1 bash コマンド
----------------------------
1 シェルとシェルスクリプト
シェルのプログラミングを始める前に、シェルとシェルスクリプトについて学びます。
シェル
カーネルは OS の基本部分であり、ハードウェアを操作するほかさまざまな機能を司っています。
シェルとは、貝殻 (=Shell) という意味です。カーネルが提供する機能を操作する際に、OS と対
話的に操作する必要があります。シェルは OS の、特にカーネル部分を包み込んでいることからそ
の名があり、対話機能を提供するものです。シェルはコマンドの入力を受け付けそのコマンドを実
行し、入力したユーザに対しその結果を返す役割があります。
シェルスクリプト
/etc ディレクトリと/home ディレクトリを圧縮して外部サーバーにコピーするといった流れを考
えてみましょう。実際に実行するコマンドを列挙するとたとえば以下のような方法が考えられます。
# tar cvzf 120626-etc.tar.gz /etc
# tar cvzf 120626-home.tar.gz /home
# scp 120626-etc.tar.gz root@backup.local.example.com:~/backup
# scp 120626-home.tar.gz root@backup.local.example.com:~/backup
これを実行のたびに入力するのは面倒です。さらに 1 つのコマンドの処理が終わるまで次のコ
マンドを実行できないため効率がよくありませんし、そもそも実行する際に端末の前に人手が必要
です。
このような繰り返し行なう処理を自動化するための手段としてシェルスクリプトを書いて実行さ
せる方法があります。
先ほどの一連処理を system-backup.sh という名前のファイルを作成して書き込み、systembackup.sh を実行することでファイルに書き込んだコマンドを順に実行します。
#!/bin/bash
tar cvzf 120626-etc.tar.gz /etc
tar cvzf 120626-home.tar.gz /home
scp 120626-etc.tar.gz root@backup.local.example.com:~/backup
scp 120626-home.tar.gz root@backup.local.example.com:~/backup
2 プログラミング
コンピュータに対して、指示を与え順番に実行させる機能を、プログラムといいます。そのプロ
グラムを作成することをプログラミングといいます。今まで学習したコマンドを、条件分岐や繰り
返しなど制御機能を加え実行することができます。これをシェルスクリプトといいます。
2.1 プログラムの例
プログラムという言葉を聞いて、最初に思いつくのは、運動会や演劇会のプログラムかもしれま
せん。また、新聞に掲載されているテレビの番組表もプログラムです。何かが順序立てて進むもの
を、プログラムといいます。では、実際にプログラムを作成するとなると、どのような例があるで
しょうか? 簡単な例だと、テレビ番組の録画予約があります。開始時刻から終了時刻とチャンネル
を指定し、画質を指定したりもします。少し複雑な例ですと、エアコンの設定で「**度に達したら
冷房を入れて、**度を下回ったら冷房を止める」などの条件を設定するというのも、一種のプログ
ラムです。
2.2 プログラムの要素
プログラミングには、どの種類のプログラムでもいくつかの重要な構成要素があります。プログ
ラムの要素は主に以下の 4 つです。
・ 順次実行
・ 条件分岐
・ 繰り返し
・ サブルーチン
これらをしっかりマスターしてください。
3 シェルスクリプト
それでは実際にシェルスクリプトを作成してみましょう。ここではシェルスクリプトを作成して
実行する手順を紹介します。
3.1 シェルスクリプトの作成
シェルスクリプトは、テキストで記述します。vi 等のテキストエディタを利用してください。
lsdate.sh というファイルに、ls と date コマンドを実行するシェルスクリプトを記述してみま
しょう。
○実行例
$ vi lsdate.sh
(lsdate.sh というファイルを作ります)
vi 上で以下のようなスクリプトを記述して、保存終了してください。
#!/bin/bash
ls
date
シェルの指定
1 行目に#!/bin/bash と記述しました。ファイルの 1 行目には、利用するシェルの種類とそのコ
マンド位置を記述します。シェルには数種類ありますが、今回は bash を使用します。
1 行目に利用するシェルを指定、2 行目以降に実行するコマンドを 1 行ずつ入力していきます。
パーミッションの変更
作成したシェルスクリプトを実行するには、パーミッションを変更してファイルの実行権限をつ
ける必要があります。例のようにシェルスクリプトに実行権限がついているか確認してみましょう。
ls コマンドでファイルのアクセス権限を確認します。
○実行例
$ ls -l lsdate.sh
-rw-rw-r--. 1 guest guest 21 Jun 6 09:51 lsdate.sh
実行権限を付与するために chmod コマンドを使います。
実行例
$ chmod 775 lsdate.sh
$ ls -l lsdate.sh
-rwxrwxr-x. 1 guest guest 21 Jun 6 09:51 lsdate.sh
これで所有者に実行権限が与えられました。実行権限をシェルスクリプトにつけたら早速実行し
てみましょう。
○実行例
$ ./lsdate.sh
Desktop diff2 ls-l-output ls-usr-bin touched-file
cat-output hosts.bak ls-l-output-second lsdate.sh uniq-sample
diff1 hosts.new2 ls-output score (ls の実行結果)
Fri Jun 6 09:55:05 JST 2008 (date の実行結果)
./というのはパス指定です。意味は「カレントディレクトリにある (lsdate.sh を実行せよ)」とい
うことです。ls や cp を実行するときは、パスが通っているので、このようなパス指定は必要ありま
せん。今回は、カレントディレクトリにある特定のシェルスクリプトを実行させるために、パス指
定しました。lsdate.sh の中に記述された、ls コマンドと date コマンドが順に実行されたことがわ
かります。
3.2 コメント
コメントとは、プログラム上に書く注釈のことです。シェルの場合は#で始まる行がコメントと
して認識され、プログラムの実行時はコメントは無視されます。コメントは多くの場合、プログラ
マーが記述したプログラムがどういった処理をするのかを記述したり、一時的に特定の処理を無効
化(コメントアウト)する場合に利用します。
実習: コメントがあるスクリプトの実行
先ほど作成した lsdate.sh を編集して、date コマンドをコメントアウトします。
○実行例
$ vi lsdate.sh
#!/bin/bash
ls
#date
シェルスクリプトを実行
○実行例
$ ./lsdate.sh
Desktop diff2 ls-l-output ls-usr-bin touched-file
cat-output hosts.bak ls-l-output-second lsdate.sh uniq-sample
diff1 hosts.new2 ls-output score (ls の実行結果)
date コマンドの出力がなくなったのがわかります。
3.3 echo コマンド
echo コマンドは引数で与えた文字列を標準出力に出力するコマンドです。
書式
echo [オプション] 文字列
オプション
-n
改行を抑制します。通常の出力は改行されますが、このオプションがあると改行されません。
○実行例
$ echo Message test
Message test
(echo コマンドで指定された文字列を表示する)
3.4 変数
プログラミングをする上で、非常に重要な考え方が、変数です。変数は、簡単に言うと「ハコ」で、
中に数値や文字列が入ります。ちょうど、中学生の数学のときに習った x や y がそれにあたります。
シェルスクリプトプログラミングでは、変数に数値や文字列を代入し、それを利用することができ
ます。変数の代入は「=」を使って行ない、参照は「$」をつけて行います。
実習: シェル変数の作成
シェル変数 abc に値を設定し、echo コマンドで内容を確認してみましょう。
○実行例
$ abc=123
$ echo $abc
123
(abc の内容を表示する)
変数 abc に 123 を代入しました。
bash では、一次元の配列変数を使用することができます。要素
は角括弧 [] で囲みます。配列変数の内容を表示する場合は、$の後ろに波括弧{}で配列変数を囲み
ます。
○実行例
$ abc[0]=123
$ abc[1]=456
$ echo ${abc[0]}
123
(abc[0] の内容を表示する)
$ index=1
$ echo ${abc[$index]}
456
(abc[1] の内容を変数を使って表示する)
算術演算にはexprコマンドを用います。exprコマンドは式を評価するコマンドです。計算する際の注意点は以下の通りです。
・演算子はくっつけてはいけません。
・シェルにとって特別な意味がある記号は\でエスケープしなくてはなりません。
・整数の計算しか行いませんので割り算の余りは切り捨てられます。
○実行例
$ echo `expr ${abc[0]} + ${abc[1]}`
579
$ x=`expr ${abc[0]} + ${abc[1]}`
$ x=`expr $x \* 2`
$ echo $x
1158
$ echo `expr $x / 5`
231
シェル変数と環境変数
シェルには、2 種類の変数があります。シェル変数と環境変数です。シェル変数は、実行してい
るシェルの内部でのみ有効です。環境変数は、そこから実行されたコマンド内でも有効になります。
環境変数は、シェル変数から作成できます。
実習: 環境変数の作成
export コマンドを使って、環境変数を作成してみましょう。
○実行例
$ export abc
(シェル変数 abc を環境変数 abc にする)
$ export xyz=234
(環境変数 xyz を作成し 234 を代入する)
一つ目のコマンドでは abc という環境変数を作成しています。abc には値を代入していません。
二つ目のコマンドでは xyz という環境変数を作成しています。xyz には 234 を代入しています。
次に、2 つのスクリプト BBB.sh と CCC.sh を使って、シェル変数と環境変数の動作の違いにつ
いて確認してみましょう。
○実行例
$ vi BBB.sh
#!/bin/bash
xxx=123 # シェル変数 xxx に 123 を代入する
export yyy=234 # 環境変数 yyy に 234 を代入する
echo xxx=$xxx in BBB.sh # 変数 xxx の値を表示する
echo yyy=$yyy in BBB.sh # 変数 yyy の値を表示する
./CCC.sh # CCC.sh を実行する
$ vi CCC.sh
#!/bin/bash
echo xxx=$xxx in CCC.sh # 変数 xxx の値を表示する
echo yyy=$yyy in CCC.sh # 変数 yyy の値を表示する
$ ./BBB.sh
xxx=123 in BBB.sh
yyy=234 in BBB.sh
xxx= in CCC.sh
yyy=234 in CCC.sh
$
このシェルスクリプトを実行した時、シェルスクリプト CCC.sh の中で xxx の値は表示されませ
ん。シェル変数は引き継がれないからです。一方で、yyy は環境変数なので CCC.sh まで引き継が
れるため、値が表示されます。
3.5 read コマンド
read コマンドは、標準入力からデータを読み込みます。すでに変数にデータが入っていた場合、
新しいデータに上書きされます。
書式
実習: read コマンドの実行
read コマンドを使ってシェル変数の内容を変更してみましょう。
○実行例
$ echo $abc
123
(シェル変数 abc の中身が表示されます)
$ read abc
aaabbbccc
(何か入力します)
$ echo $abc
aaabbbccc
(シェル変数 abc の中身が入れ替わっています)
3.6 シェル変数
シェル変数の一覧を表示する場合は、set コマンドを利用します。また削除する場合は、unset を
利用します。
実習: シェル変数一覧の表示と削除
シェル変数の一覧表示と削除を行なってみましょう。
○実行例
$ set
(環境によっては大量に出力されます)
BASH=/bin/bash
BASHOPTS=checkwinsize:cmdhist:expand_aliases:extquote:force_fignore:hostcomplete:interactive_comments:login_shell:progcomp:promptvars:sourcepath
BASH_ALIASES=()
abc=aaabbbccc
$ set | grep ^abc
abc=aaabbbccc
(abc で始まるシェル変数のみ確認します)
$ unset abc
(シェル変数 abc を削除します)
$ set | grep ^abc
$ (abc で始まるシェル変数のみ確認します)(何も出力されません)
3.7 環境変数
現在の環境変数の一覧を表示する場合は、env コマンドを利用します。また登録済みの環境変数
を削除するときは、unset コマンドを利用します。
実習: 環境変数一覧の表示と削除
環境変数の一覧表示・削除を行なってみます。
○実行例
$ env
HOSTNAME=host1.alpha.jp
TERM=xterm
SHELL=/bin/bash
HISTSIZE=1000
(略)
$ env | grep ^xyz
xyz=234
(xyz で始まる環境変数のみ確認します)
$ unset xyz
(環境変数 xyz を削除します)
$ env | grep ^xyz
$ (xyz で始まる環境変数のみ確認します)(何も出力されません)
3.8 引用符
シェルスクリプトにおいて、文字列を引用符で囲むことができます。利用できる引用符には’(シン
グルクォート)、"(ダブルクォート)、‘(バッククォート) があり、使用される引用符により囲まれた
文字列の処理が異なります。シングルクォートで囲まれた文字列の中に、参照用の「$」付きの変数
がある場合、「$」も文字列として認識されるため、変数は展開されません。ダブルクォートの場合、引用符内の「$」付き変数は展開された文字列になります。バッククォートで囲まれた文字列はコマンドとして解釈され、このとき「$」付きの変数があれば、それを展開した上でコマンドが実行されます。引用符は入れ子が可能です。
実習: 引用符の動作確認
引用符の違いについて実際に確認してみましょう。
○実行例
$ ABC=123
$ echo 'Value of ABC is $ABC.'
Value of ABC is $ABC.
($ABC が文字として認識されそのまま表示された)
$ echo "Value of ABC is $ABC."
Value of ABC is 123.
($ABC が変数として認識され、内容である 123 が展開された)
$ XYZ=`date`;
$ echo "It is $XYZ now."
It is Thu Mar 20 06:08:14 JST 2017 now.
($XYZ に date コマンドの実行結果が入っている)
$ echo "It is `date` now."
It is Thu Mar 20 06:08:14 JST 2017 now.
(ダブルクォートで全体の文字列を囲み、バッククォートで囲んだ date コマンドを挿入して、変数 XYZ への代入を省略)
3.9 引数
シェルスクリプトは、実行時にオプションを引数として参照することができます。引数は$1, $2…
など$の後に引数の番号を指定することで参照できます。
実習: 引数出力の確認
次のように args.sh を作成して、引数呼び出しの動作を確認してみましょう。
○実行例
$ vi args.sh
#!/bin/bash
echo '$1:' $1;
echo '$2:' $2;
echo '$3:' $3;
echo '$0:' $0;
echo '$#:' $#;
$ ./args.sh aaa bbb ccc
$1: aaa
$2: bbb
$3: ccc
$0: ./args.sh
$#: 3
($1-$3 は引数、$0 は実行コマンド名、$#は、引数の数を示す)
3.10 shift 文
shift コマンドは、引数の順序をずらします。shift を実行すると、$2 が$1 に、$3 が$2 に・・・に
なります。
実際にスクリプトを作成して shift コマンドの動作を確認してみましょう。
○実行例
$ vi argsshift.sh
#!/bin/bash
echo '$1:' $1;
echo '$2:' $2;
echo '$3:' $3;
shift
echo '$1:' $1;
echo '$2:' $2;
作成したスクリプトに対して、変数を渡してみます。
$ ./argsshift.sh aaa bbb ccc
$1: aaa
$2: bbb
$3: ccc
$1: bbb
($1 が bbb に変わった)
$2: ccc
($2 が ccc に変わった)
3.11 エスケープシーケンス
プログラミング言語には、特別な扱いを受ける文字があります。例えば、echo コマンドで、「Value
of ABC is "123".」のように "(ダブルクォート) を出力する方法を考えてみましょう。
○実行例
$ ABC=123
$ echo "Value of ABC is "$ABC"."
Value of ABC is 123.
($ABC を""で囲いましたが、""が表示されません)
$ echo "Value of ABC is \"$ABC\"."
Value of ABC is "123".
(表示したい"の直前に「\」を付けることで"を表示できます)
\(バックスラッシュ)は環境によって\(円サイン)に読み替えてください。
他にも方法はないでしょうか。
○実行例
$ echo 'Value of ABC is "$ABC".'
Value of ABC is "123".
(この例のように、文字列全体を'(シングルクォート) で囲むことでも"(ダブルクォート) を表示できます)
$ echo "Value of ABC is \$ABC."
Value of ABC is $ABC.
(ダブルクォートで囲んだ文字列内で「$」をそのまま表示したい場合にも直前の「\」で可能です)
このように、シェルスクリプトでは、\(バックスラッシュ)はエスケープ文字と呼ばれ、特別な
文字で直後の1文字の扱いを変更します。使用する引用符との組み合わせで、文字の扱いを変えた
い場合に有効です。
\(バックスラッシュ)を行末に付与することで、文字列の途中で折り返すことができます。バックスラッシュによる折り返しは、シェルスクリプト内で使うことができます。バックスラッシュは本来\ですが、多くの日本語環境では¥と表示されるので、ここでも¥で表示しています。環境によっては\と表示されます。
○実行例
$ vi escape.sh
#!/bin/bash
echo "I am a cat. \
As yet I have no name."
$ ./escape.sh
I am a cat. As yet I have no name.
\(バックスラッシュ)には、他の用途もあり、次に続く1文字とセットで特別な意味を持つ文字
列とする、エスケープシーケンスという手法があります。
よく使用されるものとして、¥t(タブ)、¥n(改行)、¥ooo(o は数字で8進数表記の文字) があり
ます。
echo コマンドでエスケープした文字を解釈するオプション -e で試してみましょう。
○実行例
$ echo -e "I am a cat. \nAs yet I have no name\041"
I am a cat.
As yet I have no name!
(\n は改行に変わり、\041 は!になりました。)
3.12 source コマンド
source コマンドは、bash などのシェルの内部コマンドで、指定されたファイルを読み込んでシェ
ル環境を設定します。ファイル内容はシェルコマンドと解釈して実行します。
一般的な用途としては、シェルの環境設定ファイルである".bashrc"や".bash_profile"などを設定
変更後、ログインしなおさずに設定を現在のシェル上で有効にする場合に使われます。
実習:sourceコマンドの効果
setsh というシェルスクリプトを作成、$abc という変数に xyz を定義して、source
コマンドで読み込んでみましょう。
○実行例
1. シェルスクリプト「set.sh」を用意
$ vi set.sh
(set.sh に記述)
#!/bin/sh
abc=xyz
echo $abc
2. $abc を echo コマンドで出力
$ echo $abc
($abc には何も格納されてないため、何も表示されない)
3. set.sh スクリプトを実行
$ ./set.sh
xyz
(スクリプト内で設定された変数 abc の値が echo で出力された)
set.sh には echo 文が記述されているため、その値が出力される。
4. $abc を echo コマンドで出力
$ echo $abc
(変数 abc への値の設定はスクリプト内でしか有効でないので、何も表示されない)
5. source コマンドで set.sh を読み込む
$ source set.sh
xyz
(スクリプト内で設定された変数 abc の値が echo で出力された)
6. source コマンドで読み込んだことにより、set.sh の終了後も変数 abc に値が格納されたまま
4 条件分岐
プログラミングでは、条件にそって挙動を切り替える「条件分岐」を多用します。条件分岐はす
べてのプログラミング言語に存在する機能です。もちろんシェルスクリプトでも利用することがで
きます。
4.1 if 文
比較などによる条件分岐を行なう場合は if 文を利用します。文法は次の通りです。
書式
if 条件式 1 then ... elif 条件式 2 then ... else ... fi
elif... の部分と else... の部分は省略可能です。elif は、別の条件 (条件式 2) で判断したい場合に利
用します。else は条件が全てあてはまらなかった場合、実行されます。if 文は fi で終了します。
条件式 if 文で利用される条件式には、次のような文法があります。
表 4.1 文字列比較を行なう演算子
表 4.2 数値比較を行なう演算子
4.2 ファイル属性の確認
ファイル属性の確認は次のように行います。
書式
if test -d パス ; then.....
-d の部分がファイル属性確認の演算子にあたります。-d はディレクトリであるかの判定を行な
うので、この if 文全体で、「パスがディレクトリであれば真の値を返す」という条件式になります。
ファイルの属性確認演算子は次の以下のようなものが利用できます。
表 4.3 ファイル属性の確認を行なう演算子
なお、test コマンドは [ ] を使って記述することもできます。
書式
if [ 条件節 ]; then ...
if test 条件節 ; then ....
ファイルに関する比較演算子は属性以外にもあります。詳細については man コマンドで test コ
マンドの項目を参照してください。
test を使った if 文を紹介しました。2 つの if 文を見ると、[と test が同じ位置にあること に気がつくと思います。実は、test も[も「次に条件節をとる」という同じ意味を持ってお り、ほとんど機能としては同じ働きをします。test がコマンドであるように、[もコマンドに なっており、実行することが可能です。which [と実行すると、インストールされている パスが表示されます。
つまり、if文で条件分岐を行うときは以下の書式が適切です。
書式
if [条件節1]; then ... elif [条件節2]; then ... else ... fi
4.3 複数の条件を重ねる
条件分岐の場合、複数の条件を重ねることができます。条件 A と条件 B が同時に成立している必
要があるときは、「条件 A”かつ”条件 B が成立」ということで、論理積 (=AND) が用いられます。
同じく「条件 A”もしくは”条件 B が成立」の場合は、論理和 (=OR) が用いられます。シェルス
クリプトにおいて、論理積・論理和の書き方は 2 通り存在します。
論理積
論理積は-a を用いる場合と、&&を用いる場合があります。それぞれの利用形式は次の通りです。
書式
[条件 A -a 条件 B -a 条件 C ] ....
[条件 A] && [条件 B] && [条件 C] ...
-a と&&は、それぞれ [ ] の内側か外側かで、使われ方が変わることに気をつけてください。
論理和
論理和を表す記号は、やはり 2 通り存在します。-o と||です。利用形式は次の通りです。
書式
[条件 A -o 条件 B -o 条件 C ] ....
[条件 A] || [条件 B] || [条件 C] ...
4.4 一対多の条件分岐
一対多の分岐を行なうことを考えてみましょう。if 文を用いた場合、一対多の分岐は以下のよう
になります。
if [条件節1]; then
.....
elif [条件節2]; then
.....
elif [条件節3]; then
.....
.....
fi
入力した値が0より大きい場合「正の数です」、0より小さい場合「負の数です」、0の時「0です」と表示するスクリプトを考えてみましょう。
○実行例
$ vi branch.sh
#!/bin/bash
echo -n "数値を入力"
read num
if [ $num -lt "0" ];
then
echo "負の数です"
elif [ $num -gt "0" ];
then
echo "正の数です"
else
echo "0です"
fi
これを実行すると、以下のような実行結果になります。
$ ./branch.sh
数値を入力
-3
負の数です
シェルスクリプトでは、case 文が用意されており、一対多の分岐がわかりやすく記述できるようになっています。
書式
case 変数 in
値 A)
処理 1;;
値 B)
処理 2;;
esac
変数の値が値 A のとき、処理 1 が実行されます。最後は case 文の逆からの記述である esac で終
わっていることが重要です。値はパイプ記号(|)で区切って複数指定することができます。
○実行例
$ vi case.sh
(次のように case.sh を作成して実行します)
#!/bin/bash
case $1 in
a|A)
echo "引数に a または A が入力されました";;
b|B)
echo "引数に b または B が入力されました";;
esac
これを実行すると、以下のような実行結果になります。
$ ./case.sh a
引数に a または A が入力されました
$ ./case.sh B
引数に b または B が入力されました
また、どの値にもマッチしなかった場合の処理を記述するには、値にアスタリスク(*)を用い
ます。
○実行例
$ vi defaultcase.sh 次のように defaultcase.sh を作成し、実行します。
#!/bin/bash
case $1 in
1)
echo "引数に 1 が入力されました";;
2)
echo "引数に 2 が入力されました";;
*)
echo "1,2 以外が入力されました";;
esac
これを実行すると、以下のような実行結果になります。
$ ./defaultcase.sh 1
引数に 1 が入力されました
$ ./defaultcase.sh 2
引数に 2 が入力されました
$ ./defaultcase.sh 0
1,2 以外が入力されました
(どの条件にもマッチしない場合、値*の処理が実行されます)
5 繰り返し
プログラミングにおいて、条件分岐と同じくらい重要な機構が、この繰り返しです。同じ処理を
繰り返し行い、ある条件が成立したときに終了する、という形式が用いられています。シェルスク
リプトで用いられている繰り返しは、以下の 3 通りです。
5.1 for 文
for 文は値を列挙し、それを対象に処理を繰り返します。
書式
値のリストとは、文字を羅列したものもあれば、実行結果を使うこともできます。
○実行例
$ for i in a b c d
> do
> echo $i
> done
a
b
c
d
これは、値のリストとして’a b c d’が渡され、まず i=a を行い echo $i、次に i=b を行い echo $i
を・・・と繰り返した結果です。
値のリストとしてコマンドの実行結果を使うことができます。以下のように i に ls の実行結果を代入してループが実行されます。
○実行例
for i in `ls`
> do
> echo $i
> done
(lsの結果が表示される)
5.2 while/until 文
while 文は、条件が成立している間ループを繰り返す(条件が成立しなくなったら終了)という処
理で利用されます。until 文はそれの反対で、条件が成立してない間ループを繰り返す(条件が成立
したら処理を終了)、という用途に用いられます。while true で無限ループとなりますので、do文の処理の中でbreakしてください。
書式
while 条件式
do
処理
done
until 条件式
do
処理
done
C 言語の for のようなループ文をシェルスクリプトで実現するには、expr コマンドを用いてルー
プカウンタ用の変数をインクリメント(またはデクリメント)しながら、while/until 文で処理を行
ないます。
次のような内容の loop.sh を作成し、実行してみましょう。
○実行例
$ vi loop.sh
#!/bin/bash
count=1
while [ $count -le 10 ]
do
echo "この処理は$count 回実行されました"
count=`expr $count + 1`
done
count=1 で、カウンタ用の変数 count に初期値 1 を設定します。while [ $count -le 10 ] で、
シェル変数 count が 10 以下の間、処理を繰り返します。
これを実行すると、以下のような実行結果になります。
$ ./loop.sh
この処理は 1 回実行されました
この処理は 2 回実行されました
この処理は 3 回実行されました
この処理は 4 回実行されました
この処理は 5 回実行されました
この処理は 6 回実行されました
この処理は 7 回実行されました
この処理は 8 回実行されました
この処理は 9 回実行されました
この処理は 10 回実行されました
5.3 select 文
select 文は、ユーザに対し数値による入力を促します。
書式
select 変数 in リスト
do
処理
done
実際に動作例を示してみます。
○実行例
$ vi select.sh
#!/bin/bash
select name in "apple" "banana" "orange"
do
echo "You selected $name";
done
これを実行すると、以下のような実行結果になります。
$ ./select.sh
1) apple
2) banana
3) orange
$? 1
You selected apple
(「Ctrl」C で中止)
1-3 を入力すると、do~done 内部が実行されます。リストで指定した文字列が出力されます。
5.4 繰り返しの制御
break や continue を用いることで、繰り返しを制御することができます。break は繰り返しを終
了して、continue は繰り返しの先頭に戻る役割があります。
○実行例
$ vi sample.sh
#!/bin/bash
while true
do
echo "Continue? (y/n)"
read input
case $input in
n) break
;;
y) continue
;;
*) echo "Please input y or n."
;;
esac
done
これを実行すると、以下のような実行結果になります。
$ ./sample.sh
Continue? (y/n)
y
(y を入力)
Continue? (y/n)
(繰り返しの先頭に戻る)
a
(y,n 以外を入力)
Please input y or n.
Continue? (y/n)
n
(n を入力)
$
(繰り返しを終了する)
6 サブルーチン
プログラミングをする上で、一連の処理をまとめて、再利用できるようにしたものを、サブルー
チンといいます。サブルーチンは言語体系によりさまざまな呼ばれ方をしていて、シェルスクリプ
トでは関数と呼ばれています。
6.1 関数
関数は、引数とよばれるデータを与え、処理をして結果を返すという機能の集まりです。書式は
次のようになっています。
書式
両者とも働きは同じです。引数は、実行時に関数名に続けて記述します。引数は、関数の内部で
$1, $2 で参照することができます。
6.2 return 文
シェルスクリプトの関数で、結果を返すときは return 文を実行します。
書式
return 文を実行すると、関数内での処理はそこで終了し、変数が関数の呼び出し元に返されます。
7 実際のシェルスクリプト
それでは、実際に稼働しているシェルスクリプトを見てみましょう。(今回は CentOS 7 の起動ス
クリプトを例にあげています。他のディストリビュージョンおよび CentOS のバージョンによって
は若干内容が異なる場合があります。)
7.1 起動スクリプト
今回は/etc/init.d/network を参考にします。CentOS 7 では、サービスの起動は SysV init から
systemd に置き換えられましたが、/etc/init.d に SysV init 後方互換のスクリプトがあります。
17 行目
$ cat -n /etc/init.d/network | head -n 20
17 行目
17 . /etc/init.d/functions
/etc/init.d/functions というファイルを読み込んでいます。/etc/init.d/functions は、/etc/の中に
入っているさまざまなシェルスクリプトが、共通して利用できる便利な関数が入っています。それ
を有効にするために、最初の処理として 17 行目が実行されます。
34 行目は、下記の書式に基づいた動作をします。
$ cat -n /etc/init.d/network | head -n 40 |tail -n 10
33~34 行目
33 # if the ip configuration utility isn't around we can't function.
34 [ -x /sbin/ip ] || exit 1
書式
コマンド1 && コマンド2
コマンド1が正常終了した場合、コマンド2を実行
コマンド1 || コマンド2
コマンド1が異常終了した場合、コマンド2を実行
コマンド1 && コマンド2 && ... && コマンドN || コマンドY
コマンド1から実行し、正常終了する限り && の右に進み、異常終了した段階でコマンドYを実行
最初の文字「[ 」は、/usr/bin ディレクトリにあるコマンドで、test コマンドと同じ機能を持ち
ます。また、bash のビルトインコマンドとしても存在します。
書式に従い、34 行目は[ -x /sbin/ip ]が異常終了した場合に「||」の右側のコマンド「exit 1」を
実行します。正常終了した場合は次の行に進みます。つまり、/sbin/ip が実行可能なファイルとし
て存在すれば、処理を先に進めますが、/sbin/ip を実行できない条件では異常として終了します。
exit の後に続く数値は、シェルスクリプトの終了コードです。終了コードは親プロセスに返すの
でリターンステータスとも呼ばれます。通常はスクリプトが正常に終了する場合に 0 を、それ以外
の場合にエラーコードを示す値を設定します。省略した場合には 0 が設定されます。設定された値
は特殊変数「?」に格納され、「$?」にて参照することができます。
34 行目は、test コマンドを使用して下記のように書き換えることができますが、[ ] を使用する
ことで、簡潔に記述できる利点があります。
if test ! -x /sbin/ip ; then
exit 1
fi
64~67 行目
$ cat -n /etc/init.d/network | head -n 70 |tail -n 10
64~67 行目
#tell NM to reload its configuration
if [ "$(LANG=C nmcli -t --fields running general status 2>/dev/null)" = "running" ]; then
nmcli connection reload
fi
nmcli コマンドの前に LANG=C (英語表記)をつけています。Linux は、国際化された OS で、言語設定(locale) によって、出力メッセージの言語を切り替えることができます。上記では、nmcli -t --fieldsrunning general status 2 の結果が running であるか否かで分岐していますが、利用者の言語設定によってコマンドの結果文字列が変わってしまうのを回避するために、このコマンド実行でのみ強制的に LANG=C に設定しています。言語設定が異なると下記のように結果が変わります。よく使
われるテクニックなので覚えておくと役に立ちます。
(LANG に POSIX のデフォルトロケールの C を指定した場合)
$ LANG=C nmcli -t --fields running general status 2
running
(LANG にイタリア語のロケールの it_IT を指定した場合)
$ LANG=it_IT nmcli -t --fields running general status 2
in esecuzione
7.2 関数のシェルスクリプト
/etc/init.d/functions というファイルがあります。/etc/init.d/network の 17 行目でも説明した
とおり、シェルスクリプトの便利な関数を集めたシェルスクリプトです。その中で記述されている
関数を 1 つ説明します。
624 行目
$ cat -n /etc/init.d/functions | head -n 632 |tail -n 10
624 行目
is_ignored_file() {
case "$1" in
*~ | *.bak | *.old | *.orig | *.rpmnew | *.rpmorig | *.rpmsave)
return 0
;;
esac
return 1
}
is_ignored_file() という関数は、/etc/init.d/network の中で無視したいファイル名に該当する
かどうかのチェックを行っており、引数の 1 つ目 ($1) にファイル名が代入されます。case 文で引数に格納されたファイル名が *~, *.bak, *.orig, *.rpmnew, *.rpmorig, *.rpmsave (*は 0 文字以上の任意の文字列を意味します)のいずれかの条件に該当していれば 0(真)を返し、該当していなければ 1(偽)を返します。
無視したいファイル名が増えた場合、例えば.abc で終わる場合が出たとします。そのときは*.abc
のエントリをここで加えると、/etc/init.d/network のみならず、is_ignored_file() を呼んでいる関
数全部で同等の機能が反映されます。関数の利用は、呼ばれているプログラムの機能を一括で更新
できる、という利点があることを理解してください。
この関数は/etc/init.d/network 以外でも利用できます。
8 デバッグ
作成したプログラムが思ったように動作しない場合、どこに問題があるのか調べる必要がありま
す。これをデバッグといいます。プログラムの所々に、変数を表示させるコードや、プログラムの
走行ルートを表示させるコードを埋め込む必要があり、非常に労力がかかります。よって多くのプ
ログラム言語には、デバッグを支援するツールが用意されています。シェルスクリプトにも、スク
リプトをデバッグモードで動かすためのコマンドが用意されています。
8.1 sh コマンド
sh 自身はシェルを起動するコマンドですが、-x オプションを付けて引数にシェルスクリプトを指
定すると、コマンドや変数の中身を表示しながらスクリプトを実行します。繰り返しの項で使用し
た sample.sh を sh コマンドにて実行してみましょう。
○実行例
$ sh -x ./sample.sh
+ true
+ echo 'Continue? (y/n)'
Continue? (y/n)
+ read input
y
(y を入力)
+ case $input in
+ continue
+ true
+ echo 'Continue? (y/n)'
Continue? (y/n)
+ read input
n
(n を入力)
+ case $input in
+ break
sh コマンドは -n オプションを利用するとシェルはスクリプトを1行ずつ読み込んで解釈を行いますが、実行はしません。そのためスクリプトの記述に文法エラーがないかを実行前に確認することができます。sample.shの中身を修正して文法チェックでエラーを出力してみましょう。
○実行例
vi cat sample.sh
#!/bin/bash
while true
do
echo "Continue? (y/n)"
read input
case $input in
n) break
;←ここでエラー
y) continue
;;
*) echo "Please input y or n."
;;
esac
done
これを実行すると以下のような実行結果になります。
$ sh -n sample.sh
sample.sh: 行 8: 予期しないトークン `;' 周辺に構文エラーがあります
sample.sh: 行 8: `;←ここでエラー'