shell script
シェルスクリプト

講義

シェル

ターミナルに対して入力するコマンドを処理するプログラムがシェルである. シェルの機能について説明する.

リダイレクト

通常,コマンドの入力はキーボードであり,出力は画面に設定されている.リダイレクトにより それらをファイルに切り替えることができる.入力の切り替えは "<", 出力の切り替えは ">" で行う.たとえば ls コマンドはカレントディレクトリの ファイル,ディレクトリの一覧を表示するコマンドであり,通常は画面に出力を 表示するが,"ls > file1" とコマンドを入力すると ls コマンドの結果が file1 というファイルに書き込まれる.

パイプ

あるコマンドの出力を直接別のコマンドの入力へ繋げることができる. コマンド 1 の出力をそのままコマンド 2 の入力として使いたい場合, コマンド1 | コマンド2と入力する.例えば ps axu コマンドで現在実行されている プロセスの情報が画面に出力される.この中で "firefox" という文字列がある 行だけを表示したい場合を考える. ps axu コマンドの出力を grep "firefox" の入力とすればよいので "ps axu | grep firefox" とすればよい.

シェルスクリプト

何度もシェルに同じコマンドを実行させるときや,複雑な 処理を行うときなどのために,実行させるコマンドをあらかじめファイルに 記述しておき,それを呼び出して実行することができる. シェルに実行させるコマンドを記述したファイルをシェルスクリプトとよぶ.

シェルスクリプトの実行

ターミナルでディスプレイに Hello world! と表示させたい場合, 文字列を出力するコマンド echo を利用して, 以下のようにコマンドを入力すればよい.

user1@pc001:~$ echo 'Hello world!' 
練習4.1

上記コマンドを実行せよ.

同じことを を実行するシェルスクリプトを作成する場合, 以下の内容のファイルを作成する.ファイル名は何でもよい.ここでは ファイル名を hello.sh として自分のホームディレクトリに 保存する.

例 4.1 hello.sh

#!/bin/sh

echo 'Hello world!'
練習4.2

gedit を使用して上記内容のファイル hello.sh を自分のホームディレクトリに 作製せよ.

一行目はシェルプログラムが存在する場所を示すための行で, すべてのシェルスクリプトの一行目に書く. シェルプログラムが存在する場所はシステムにより 異なる場合がある. "which sh" コマンドで sh コマンドの絶対パスを知ることができる. 実行してこのシステムでの sh コマンドの場所を確認せよ. /bin/sh が表示されるはずである.

hello.sh ファイルをコマンドとして実行可能にするには実行パーミッションを つける必要がある.hello.sh ファイルがあるホームディレクトリで ls -l を実行したとき,

-rw-r--r--  1 user1  user1   16  2010-06-27 01:00 hello.sh

と表示される場合,実行パーミッション(x)が無い状態である.

練習4.3

上記 ls を実行して hello.sh のパーミッションを確認せよ.

これを 自分には読み書き実行許可,グループと他人には読みと実行のみ許可 という設定にする場合,-rwxr-xr-x とすればよい.これの 2 進数表現は 111 101 101 すなわち 755 であるので以下のコマンドを入力すればよい.

chmod 755 hello.sh

これで hello.sh に実行属性がついたので hello.sh があるディレクトリにて ./hello.sh コマンドを入力すれば実行できる.

練習4.4

./hello.sh を実行して動作を確認せよ.

逐次実行

シェルスクリプトに複数のコマンドを列挙すると,上の行のコマンドから順に実行される. 例えば date コマンドを実行したあと ls コマンドを実行するシェルスクリプトは以下のとおりである.

例 4.2

#!/bin/sh

date
ls
練習4.5

例 4.2 を datels.sh というファイル名で保存し, 実行パーミッションをつけて(chmod 755 datels.sh), ./datels.sh を実行して動作を確認せよ.

引数

シェルスクリプトにいろいろな処理をさせたい場合,引数が与えられると便利である. 例えば引数として指定されたのファイルのコピーを,"もとのファイル名.old" という 名前で作成するスクリプト cpold.sh は以下のように作成することができる.

例 4.3 cpold.sh

#!/bin/sh

cp $1 $1.old 

$1 はスクリプトに対する第一引数を示す.例えばカレントディレクトリに file1 というファイルがある場合,"./cpold.sh file1" コマンドを実行すると file1 ファイルのコピーである file1.old が作成される.

$0 には実行されたコマンド自身(この場合は ./cpold.sh)が格納され, $1, $2,... には順に第 1 引数,第 2 引数... が格納される.

練習4.6

例 4.3 を cpold.sh というファイル名で保存し, 実行パーミッションをつけよ.先ほど作製したファイル datels.sh に対して使用する.第一引数としてファイル名を 渡せばいいので入力するコマンドは"./cpold.sh datels.sh" である.実行せよ. 同じファイル名の後ろに .old がついたコピーができていることを ls, cat で確認せよ.

逆クォート

シェルスクリプト中で,逆クォートで囲んだ場所はクォート内のコマンドの 実行結果で置換される.たとえば date コマンドは現在の日時を出力 するコマンドであるが,それを組み込んだコマンドを以下のように作ることができる.

例 4.4

#!/bin/sh

echo "date is `date`"

echo は文字列を出力するコマンドであり,この場合は date is と出力したあと date コマンドの出力内容を出力する.

練習4.7

例 4.4 を適当なファイル名で保存し,実行パーミッションを つけて実行せよ.`date` の部分が date コマンドの結果に書き換えられて 出力されることを確認せよ.

変数

シェルスクリプトでも変数を利用できる.変数名として利用できる 文字は英数字とアンダースコア"_" であるが,最初の文字には数字 を使うことはできない.

変数へ値を代入するには"変数名=値"と書けばよい. イコールの両側にスペースを入れてはいけないことに気をつける. 変数の値を呼び出すには変数名の前に "$" をつける.次の例は 変数 name に値 user1 を設定し, echo 文でそれを出力する スクリプトである.

例 4.5

#!/bin/sh

name=user1
echo "name is $name"
練習4.8

例 4.5 を適当なファイル名で保存し,実行パーミッションを つけて実行せよ.$name の部分が user1 に置き換えられて 出力されることを確認せよ.

例えば $name の後に x を付け足したい場合,$namex と書くと $namex という名前の変数と解釈されてしまう.これを避けるため, {} を使って変数名の範囲を明示的に指定することができる. この例では ${name}x と書けばよい.

date コマンドのオプションとして,"date +%Y%m%d" とコマンド入力することで日付を 年,月,日の8 桁で出力させることができる.

練習4.9

コマンドラインから date +%Y%m%d コマンドを実行し,出力を確認せよ.

このコマンドと逆クォート,変数, コマンドライン引数を使用することで引数で与えられたファイルのバックアップとして 拡張子に日付を付加したファイルを作成するスクリプト bup1.sh を書くことができる.

例 4.6 bup1.sh

#!/bin/sh

TIME=`date +%Y%m%d`
cp $1 $1.$TIME
練習4.10

bup1.sh というファイル名で例 4.6 のシェルスクリプトを作製し, 実行パーミッションをつけよ.練習 4.6 の cpold.sh コマンドと同様に 第一引数に存在するファイル名を与えて実行し,ls, cat で コピーの結果を確認せよ.

条件分岐

条件分岐は本来は

if 条件式
then
    条件式が真の時の処理
else
    条件式が偽の時の処理
fi

の形式で行う.else 節は処理を行わない場合は省略できる. 実際には条件式は [...] の構文を用いることが多いのでその 代表的にな使い方をいくつか説明する.

先述のようにシェルスクリプトへの第 1 引数は $1 に格納され,第 2 引数以降は $2, $3 ... に格納される.例 4.6 のバックアップシェルスクリプト bup1.sh を改良し,$1 の内容が "-l" だった場合に "ロングフォーマット", すなわち "年月日-時分" を拡張子として追加する bup2.sh を作成する.この場合, バックアップするファイル名は第 2 引数で与えられるとする. 例えば file1 に対して 2010 年 6 月 28 日 9:20 に bup2.sh file1 を実行すると今まで通りコピーとして file1.20100628 が作成され,bup2.sh -l file1 を実行すると file1.20100628-0920 が作成されるようにしたい.このスクリプトは 条件式の [...] 記法と $2 以降すべてについて番号を 1 小さいものにセットしなおす操作である shift を利用して以下のように記述することができる.

例 4.7 bup2.sh

#!/bin/sh

if [ $1 = "-l" ]
then
    TIME=`date +%Y%m%d-%H%M`
    shift
else
    TIME=`date +%Y%m%d`
fi

cp $1 $1.$TIME

shift により $2, $3,... に設定されていた値が 1 つずつ左に"シフト"され, $1, $2,... に設定される.

ほかに if がよく利用される場面としてファイルが存在するかどうかで処理を変える場合が ある.これには [ -e ...] 記法を使う.たとえば第 1 引数のファイルがあればその内容を表示, 無ければ無いことを示すメッセージを出力するスクリプトは以下のようになる.

例 4.8

#!/bin/sh

if [ -e $1 ]
then
    cat $1
else
    echo "cannot find $1."
fi

繰り返し

シェルでは繰り返し構文として while, until for が使用できる. while, until の構文はそれぞれ以下の通りである.

while 条件式
do
    処理
done
until 条件式
do
    処理
done

while では条件式が真の間,until では条件式が偽の間処理が繰り返される.

シェルスクリプトでは繰り返しとしては for がよく利用される. 例えば file1, file2, file3 について 順に ls -l を行うコマンドは 以下のように書ける.ここでは $i がループ変数の役割をしている. for の行ではループ変数名に $ がつかないことに注意せよ.これは変数への セットでは $ をつけず,読み出し時に $ をつけるのと同じである.

#!/bin/sh

for i in file1 file2 file3
do
    ls -l $i
done

$* を使って引数すべてを for で処理することができる. $* と書くと,すべての引数($1 以降)をスペースで区切って繋げた文字列 として解釈できるので for 分の in 以降にそのまま使うことができる. 以下は bup2.sh を改良し,複数のファイルを引数としてとることのできる バックアップコマンド bup3.sh である.

例 4.9 bup3.sh

#!/bin/sh

if [ $1 = "-l" ]
then
    TIME=`date +%Y%m%d-%H%M`
    shift
else
    TIME=`date +%Y%m%d`
fi

for i in $*
do
    cp $i $i.$TIME
done

上のスクリプトで cp を cp -r に変えるとファイルだけでなくディレクトリのバックアップも できるスクリプトを作ることができる.

実習

実習 1

/ ディレクトリにあるファイルのリストをカレントディレクトリの file1 に書き出し,その書き出したファイルに対して sort -r file1 を実行した結果をカレントディレクトリの file2 に書き出すシェルスクリプトを作成し,実行せよ. sort は -r オプションを与えると逆順にソートを行う.

実習 2

例 4.8 のシェルスクリプトを作成し,引数として /etc/resolv.conf を与えて 実行せよ.

実習 3

自分の端末に登録されている 班員のアカウントすべてに対し, for ループを使って順に

を行うシェルスクリプト ex3.sh を作製せよ.

実習 4

空白で区切った複数の文字列を引数を受けとる シェルスクリプトで,受けとった文字列を 重複を許して 2個をつなげた組み合わせすべてを 画面に表示するシェルスクリプト ex4.sh を作製せよ.

例えばコマンドが以下のように入力された場合,

user1@pc001:~$ ./ex4.sh aa bb cc

出力は以下のようになればよい.

aaaa
aabb
aacc
bbaa
bbbb
bbcc
ccaa
ccbb
cccc

課題

第 3 回課題「シェルスクリプト」 を提出せよ.


Updated in October 14 , 2014, index.html, Yamamoto Hiroshi