シェルスクリプト入門

2018年6月23日

はじめに

本文書ではシェルスクリプトの書き方を説明する。特に B シェル系のスクリプトについて説明する。

シェルとは

シェルとは OS とユーザーの仲立ちをするもので、ここでは要するに UNIX 系システムで見られる端末のコマンドラインのことである。シェルにはいくつか種類があり、なにが使われているかは ps コマンドで調べることができる。

$ ps
  PID TTY          TIME CMD
 1937 pts/0    00:00:00 bash
 2344 pts/0    00:00:00 ps

上記の例ではシェルに bash が使われていることがわかる。ここではシェルに bash が使われているという前提で話を進める。

シェルスクリプトとは

シェルスクリプトというのは、シェルのコマンドを書き連ねたものである。コマンドの実行手順をまとめてひとつのコマンドとして扱うことができる。シェルスクリプトの書き方はシェルの種類によって異なることがある。

はじめてのシェルスクリプト

echo コマンドを使うと文字列を出力できる。

$ echo test
test

このコマンドを使ってシェルスクリプトを作ってみる。次のような内容のファイルを用意する。

#!/bin/sh
# test
echo test

このファイルの名前を "echo_test" としよう。これに実行権限を与える。

$ chmod +x echo_test

実行。

$ ./echo_test
test

"test" の文字列が出力される。

スクリプトの説明。

#!/bin/sh

"#" 以降の文字列はコメント扱いされる。ただし、1 行目の "#!..." は特殊で、そこに書かれたコマンドによってスクリプトが実行される、という意味である。ここでは /bin/sh によりスクリプトが実行される。

# test

これはほんとうのコメント。

echo test

コマンドの実行。

コマンドを連ねて行くと、そのとおり実行される。

#!/bin/sh
# test2
echo test1
echo test2
echo test3

これを "echo_test2" とする。

$ chmod +x echo_test2
$ ./echo_test2
test1
test2
test3

シェルの種類

上記の "/bin/sh" の sh は B シェル (Bourne シェル) というもので、bash はその後継シェルである。これとは別系統で C シェル (csh/tcsh、tcsh は csh の後継) というものがあるが、スクリプトの書き方がかなり異なる。ここでの説明を B シェル系であると断っているのはそのためである。

ところで、bash を使っているのなら、先のスクリプトの 1 行目は "/bin/bash" でよいではないかと思うかもしれない。わざわざ sh を指定するのは、sh がすべての UNIX 系システムで必ず用意されているためである。ここでの説明で C シェル系ではなく B シェル系を選択しているのも同じ理由である。

変数

つぎのように文字列に "=" で値を指定すると、変数を定義できる。

$ A=1

"=" の両側にスペースを入れてはいけない。

変数名の前に "$" をつけると、その変数の値を参照することができる。

$ echo $A
1

文字列に関連して変数を使う場合には注意が必要。

$ echo A = $A
A = 1
$ echo "A = $A"
A = 1
$ echo 'A = $A'
A = $A

変数値の参照は "..." の中では値に展開されるが、'...' の中では展開されない。

環境変数

変数には、そのシェルで定義され使用される変数と、環境変数がある。シェルスクリプトは、シェル上で別のシェルを起動して実行される。環境変数は、もとのシェルからスクリプト用のシェルに引き継がれる。

変数を環境変数にするには、export コマンドを使う。

$ export A

たとえば、つぎのような var_test というスクリプトを作る。

#!/bin/sh
echo "A = $A"
echo "B = $B"

つぎのように実行する。

$ chmod +x var_test
$ A = 1
$ B = 2
$ export A
$ ./var_test
A = 1
B =

変数 A は環境変数なのでスクリプトから参照でき、値が表示されるが、B はそうではないので未定義の変数となり、値は表示されない。

変数の定義をコマンド実行と同時に行うと、値をスクリプトに渡すことができる。

$ B=2 ./var_test
A = 1
B = 2

設定されている環境変数のリストは env コマンドにより得られる。

制御文

if 文

if 文により条件によって処理を分岐できる。

if [ 条件 1 ]
then
	条件 1 のときの処理
elif [ 条件 2 ]
then
	条件 2 のときの処理
else
	上記条件以外のときの処理
fi

つぎのような書き方をすることがある。

if [ 条件 1 ] ; then
	条件 1 のときの処理
elif [ 条件 2 ] ; then
	条件 2 のときの処理
else
	上記条件以外のときの処理
fi

セミコロン ";" は複数のコマンドを 1 行で書くための区切り文字である。

条件判定

if 文で使われる条件判定にはつぎのようなものがある。

整数

int1 -eq int2 : int1 と int2 が等しいか
int1 -ne int2 : int1 と int2 が等しくないか
int1 -lt int2 : int1 が int2 より小さいか
int1 -le int2 : int1 が int2 以下か
int1 -gt int2 : int1 が int2 より大きいか
int1 -ge int2 : int1 が int2 以上か

文字列

str1 = str2  : str1 と str2 が同じか
str1 != str2 : str1 と str2 が同じでないか
-z str       : str が空か
-n str       : str が空でないか

ファイル

-f file : file が普通のファイルか
-d file : file がディレクトリか
-s file : file が空でないか
-r file : file が読み込み可能か
-w file : file が書き込み可能か
-x file : file が実行可能か
-L file : file がシンボリックリンクか

その他

! 条件         : 否定
条件1 -a 条件2 : AND
条件1 -o 条件2 : OR

実数の比較はそのままではできないので、特殊な書き方になる。

R=`echo "$A >= $B" | bc`
if [ $R -eq 1 ] ; then
	...
fi

ここで、`...` はその中の文字列をコマンドとして実行して、その結果を文字列として取り出すものである。bc に実数の比較式を渡して結果を得ている。1 だと真、0 だと偽である。

real1 == real2 : real1 と real2 が等しいか
real1 != real2 : real1 と real2 が等しくないか
real1 <  real2 : real1 が real2 より小さいか
real1 <= real2 : real1 が real2 以下か
real1 >  real2 : real1 が real2 より大きいか
real1 >= real2 : real1 が real2 以上か

for 文

for 文で文字列のリストに対して処理を繰り返すことができる。

for 変数 in 文字列リスト
do
	処理
done

1 から 5 まで数字を表示するにはつぎのようにする。

for N in 1 2 3 4 5 ; do
	echo $N
done

break でループを抜けたり、continue でつぎのステップに進めたりできる。

for N in 1 2 3 4 5 ; do
	if [ $N = 2 ] ; then
		continue
	fi

	echo $N

	if [ $N = 3 ] ; then
		break
	fi
done

この例では 1 と 3 だけが表示される。

アスタリスク "*" でカレントディレクトリのファイルのリストを得ることができる。

for FILE in * ; do
	echo $FILE
done

つぎのようにすると、カレントにあるディレクトリに入ってそれぞれ同じ処理を行わせることができる。

for FILE in * ; do
	if [ -d $FILE ] ; then
		cd $FILE
		処理
		cd ..
	fi
done

while 文

while 文ではある条件が成り立つあいだ処理を繰り返させることができる。

while [ 条件 ]
do
	処理
done

1 から 5 まで数字を表示するにはつぎのようにする。

I=1
while [ $I -le 5 ] ; do
	echo $I
	I=`expr $I + 1`
done

expr は整数の計算を行うコマンドである。

次のようにすると、ファイルから 1 行ずつとりだして処理することができる。

cat file | while read LINE ; do
	echo $LINE
done

case 文

case 文では、文字列に対してパターンを並べ、合致したパターンの処理を実行させることができる。

case 文字列 in
パターン 1) 処理 1 ;;
パターン 2) 処理 2 ;;
	...
*) 上記パターンのどれにも合致しない場合の処理 ;;
exac

環境変数 SHELL の値に応じて処理を変える場合は、つぎのように書ける。

case `basename $SHELL` in
bash) bash 用の処理 ;;
tcsh) tcsh 用の処理 ;;
*) それ以外の処理 ;;
esac

basename は、パスの最後の文字列を取り出すコマンドである。"/bin/sh" ならば "sh" を返す。

パターンにはつぎのような特殊な書き方ができる。

*      文字列すべてに合致
?      1 文字に合致
[...]  この中に含まれるどれか 1 つの文字に合致
[!...] この中に含まれない文字に合致

また "[a-z]" で "a" から "z" までの文字、"[1-9]" で "1" から "9" までの文字、という書き方ができる。

"a" からはじまる文字列に対して処理するには、つぎのように書く。

case $STRING in
a*) 処理 ;;
	...
exac

数値の計算

シェルスクリプトでは直接数値の計算ができないので、数値の計算にはコマンドを使う。

整数の計算

整数の計算には expr コマンドを使う。

expr int1 + int2 : 和
expr int1 - int2 : 差
expr int1 '*' int2 : 積
expr int1 / int2 : 商
expr int1 % int2 : 剰余

計算結果を得るには `...` を使う。

C=`expr $A '*' $B`

実数の計算

実数の計算には bc コマンドを使う。式をパイプで渡す。

echo "scale=n; num1 + num2" | bc : 和
echo "scale=n; num1 - num2" | bc : 差
echo "scale=n; num1 * num2" | bc : 積
echo "scale=n; num1 / num2" | bc : 商
echo "scale=n; num1 % num2" | bc : 剰余
echo "scale=n; num1 ^ num2" | bc : 累乗

"scale=n" は小数点以下の出力桁数の指定で、n は任意である。

関数を使うこともできる。

$ echo "scale=3; sqrt(2)" | bc
1.414

入力

コマンド read でユーザーの入力を得ることができる。たとえば、処理を進めてよいか確認するにはつぎのように書くことができる。

echo -n "OK? (y or n): "
read R

if [ $R != "y" ] ; then
	echo "abort."
	exit 0
fi

これを実行すると、つぎのように入力待ちになる。

OK? (y or n):

コマンドライン引数

シェルスクリプト実行時に引数を指定することができる。指定した引数は変数 $1, $2, ... $9 に入る。

つぎのような arg_test というファイルを作る。

#!/bin/sh
echo $1 $2 $3

つぎのように実行する。

$ chmod +x arg_test
$ ./arg_test a b c
a b c

$1, $2, $3 がそれぞれ第 1、第 2、第 3 引数に対応しているのがわかる。

"$#" で引数の数を取得できるので、これでふつうのコマンドっぽい反応をさせることができる。

#!/bin/sh

PROG=`basename $0`
if [ $# -lt 1 ] ; then
	echo "usage: $PROG <arg>"
	exit 0
fi

echo "arg = $1"

これを arg_test2 として実行すると、つぎのようになる。

$ chmod +x arg_test2
$ ./arg_test2
usage: arg_test2 <arg>
$ ./arg_test2 1
arg = 1

ここで $0 はコマンドラインで指定したスクリプトのパス (ここでは "./arg_test2" になる)、exit はスクリプトを終了させるコマンド (引数は終了コード) である。ちなみに、他のプログラムを呼び出した際の終了コードは $? で参照できる。

コマンドライン引数を処理するのに便利かもしれないコマンドとして、shift というものがある。これは、$2 の内容を $1 に、$3 の内容を $2 に、などと引数の内容をずらす。

while [ $# -gt 0 ] ; do
	echo $1
	shift 1
done

上記のようにすると、それぞれの引数に対して $1 だけでアクセスできるため、処理を簡潔に書けるかもしれない。shift の引数はずらす個数である。

関数

つぎのようにすると、関数を作ることができる。

func()
{
	処理
}

これはコマンドのように使うことができる。

func arg1 arg2 ...

引数の扱いはコマンドライン引数と同じで、$1, $2, ... で扱う。たとえば、数値の計算をするための関数はつぎのように書ける。

calc()
{
	echo "$1" | bc
}

A=`calc "2 * 3"`
echo $A

コマンド集

echo

文字列の表示。

echo string

オプション -n で改行しないようにできる。

$ echo -n "aaa"; echo "bbb"
aaabbb

cat

本来はファイルを結合するために使う。主にファイルの内容を出力するのに使われる。

cat file1 file2 > file3

複数の行にわたる文字列を表示する場合、echo で 1 行ずつ表示する代わりに、ヒア・ドキュメントというものが使える。

cat << END
calc()
{
	echo "\$1" | bc
}
END

"END" は任意の文字列で、はじめに指定したこの文字列が出てくるまで表示が続く。変数は値に展開される。

grep

テキストの中で指定した文字列がある行を表示する。

cat file | grep word

オプション -v で逆に検索に引っかかった行を表示しない。たとえば、word1 がある行の中で word2 がある行は取り除きたい場合、つぎのようにする。

cat file | grep word1 | grep -v word2

cut

文字列の一部を取り出すことがきる。

$ echo "abcdef" | cut -c2-4
bcd

ある文字で区切られた文字列のあるフィールドを取り出すこともできる。

$ echo "ab,cd,ef" | cut -d',' -f2
cd

sed

テキストの編集ができる。

文字列の置換はつぎのようにする。

sed -e "s/string1/string2/g" file

検索語にスラッシュを含む場合は、区切り文字を他の文字で代用できる。

sed -e "s%string1%string2%g" file

上記のコマンドでは、編集結果が画面に出力される。オプション "-i" でファイルを上書きできる。

sed -i -e "s/string1/string2/g" file

オプション "-e" の引数の中身は、編集範囲、コマンド、コマンドの引数となっている。たとえば、3 行目を削除したい場合は、次のようにする。

3 d

3 行目に文字列を挿入したい場合は、次のようにする。

3 i string

2 行目の後に文字列を挿入するには、次のようにする (3 行目に追加するのと同じ)。

2 a string

string を含む行の後に文字列を挿入するには、次のようにする。

/string/ a string2

string1 から string2 までの範囲の文字列を削除したければ、次のようにする。

/string1/,/string2/ d

次のようにすると、string1 から string2 までの範囲の文字列を別の文字列に置き換えることができる。

/string1/,/string2/{
	/string1/ i string
	d
}

これは正確には、string1 を含む行の前に string を追加して、string1 から string2 までの範囲の文字列を削除している。

sort

テキストの内容を並び替える。

cat file1 | sort > file2

単語帳を昇順に並べ替えたりできる。

cat << END | sort
orange
apple
lemon
END
apple
lemon
orange

オプション -r で降順にできる。

cat << END | sort -r
orange
apple
lemon
END
orange
lemon
apple

オプション -u で重複を 1 つにまとめることができる。

basename, dirname

パスのファイル名、ディレクトリ名を取り出す。

$ echo $SHELL
/bin/bash
$ basename $SHELL
bash
$ dirname $SHELL
/bin

basename でファイル名から拡張子を取り除くことができる。

$ basename file.txt .txt

参考文献

  • ブルース・ブリン, 入門 UNIX シェルプログラミング 改訂第 2 版, ソフトバンク パブリッシング, 2003