それではコードの解説に入ります。
一番最初の関数の宣言部分は後回しで説明したいので、まず最初に以下の部分を見ていきます。
if [ $# -lt 1 ]; then
die "missing target file name"
fi
if [ -e $1 ]; then
target_file_name=$1
else
die "target file ${1} that you specified does not exists."
fi
if [ $2 ]; then
if [ -e $2 ]; then
die "backup file ${2} already exists"
else
backup_file_name=$2
fi
else
backup_file_name=`date +%Y%m%d%H%M%S`.tgz
fi
この部分を理解するためにまず、変数、特殊変数、そして条件分岐の一つであるif~then~の構文を理解する必要があります。 まずこの三つを解説します。
変数の宣言は以下のように変数名にイコール(=)をつけて値を代入します。
変数名=値
そして変数を使うときは以下のように変数名に$マークをつけることで変数の値にアクセスできます。 使うときはなるべく${変数名}のように{}で囲んだほうが安全です。
${変数名}
$変数名
プログラム上では、以下の部分が変数宣言に該当します。
target_file_name=$1 # $1の値を代入
backup_file_name=$2 # $2の値を代入
backup_file_name=`date +%Y%m%d%H%M%S`.tgz # dateコマンドの結果と文字列".tgz"を連結した結果を代入
また、以下のような場所で変数を利用してました。
target_file_name=$1 # $1の値を代入
backup_file_name=$2 # $2の値を代入
die "target file ${1} that you specified does not exists."
# $1の中身を文字列に埋め込み
die "backup file ${2} already exists"
# $2の中身を文字列に埋め込み
$1、$2は特殊変数という特別な変数ですが、ほかの変数のように$に続く変数名の中に値を持っています。
なお、変数宣言の注意点として、イコール(=)の前後にスペースがあってはいけません。
name=value # OK
name = value # NG
ここまで、$1、$2という記号が出てきましたが、これらはシェルプログラムの中では特殊変数と呼ばれています。 一般的な変数はシェルプログラミングの中でユーザが明示的に宣言をして利用していきます。特殊変数は、このような一般の変数とはちょっと異なり、プログラム自身が予約している特別な変数になります。
特殊変数が持っている主な値は、シェルプログラム自身に関する情報です。特にシェルを実行したときの引数などがこれにあたります。 上記の$1や$2にはシェルプログラム実行時の第1引数、第2引数がそれぞれ格納されています。
特殊変数には以下のようなものがあります。ちなみにこれらの特殊な変数は参照されるだけであり、値を代入することは許されません。
変数名 | 概要 |
---|---|
$0 | シェルスクリプトのファイル名 |
$1~$n | シェル引数の値。$1は第1引数、$2は第2引数…。 |
$# | シェル引数の数 |
$* | 全引数リスト。"$*"のようにダブルクオートで囲むと、"$1 $2 … $n"形式。 |
$@ | 全引数リスト。"$@"のようにダブルクオートで囲むと、"$1" "$2" … "$n"形式。 |
$$ | 実行中のシェルのプロセスID。 |
$? | 最後に実行したコマンドのリターンコード(戻り値)。通常は成功していれば0 |
$- | シェルの実行オプション(setコマンドや#!/bin/bash行に付けたオプション。 |
$! | シェルが最後に実行したバックグラウンドプロセスのPID。 |
多くの言語でサポートされているif文による条件分岐はbashでも利用可能です。
基本の構文は以下の通りです。
if 条件式1;
then
条件式1が真だったときに実行する処理
elif 条件式2
条件式1が偽で条件式2が真だったときに実行する処理
else
条件式1と条件式2が偽だったときに実行する処理
fi
elifやelseの部分は不要であればない場合もあります。 以下は条件式が一つだけの場合です。
if 条件式;
then
条件式が真だったときに実行する処理
else
条件式が偽だったときに実行するデフォルトの処理
fi
デフォルトの挙動も無い場合は以下のようになります。
if 条件式;
then
条件式が真だったときに実行する処理
fi
ちなみに、thenの部分はこれだけのために一行使うのも無駄が多いので、以下のようにするのも一般的です。
if 条件式; then
条件式が真だったときに実行する処理
fi
次に条件式の記述方法です。条件式の部分には基本的には、成功すれば0を返し、失敗したら0以外を返すコマンドを利用可能です。 ここですべてを説明してしまうと話が長くなってしまい、チュートリアルの趣旨からだいぶ外れてしまいそうなので、よく使う方法(test)を説明しておきます。
if文などの条件判定に特化したコマンドとして、testコマンドがあります。 testコマンドは引数を比較したり、引数に指定したファイルの有無をチェックしたりする条件判定でよく使う判定に特化したコマンドです。
例えば、シェルの実行パラメータとして引数が1個以上必要な場合、引数の個数をチェックして足りなかった場合に処理を停止したいといった場合があります。 これをtestコマンドを用いて表現すると以下のようになります。
if test $# -lt 1; then
die "missing target file name"
fi
さらにtestコマンドは簡略表現が用意されていて[]で条件式を囲むとtestコマンドに与えることになります。この簡略表現を利用すると上記は以下のように書き直すことができます。
if [ $# -lt 1 ]; then
die "missing target file name"
fi
シェルスクリプトの多くはこの表現を使うことが一般的です。なんとなく"if [ 条件式 ]"という表現はほかのプログラミング言語の"if()"にも似てますし、読みやすさの観点からも好まれています。 記述の際、"if [ 条件式 ]"のスペースは注意が必要です。ifの後、[の後、]の前には必ず半角スペースが必要です。
$#というのは引数の数を保持している特殊変数です。それに対して"-lt"の部分は"less than"を表していています。 全体で「引数の数が1より少なかったら~」という条件式になっています。-ltなどの部分はほかにもたくさんバリエーションがあります。
testコマンドで数値の比較の場合は以下のような演算子が利用可能です。
利用可能な演算子 | 該当する算術演算子 |
---|---|
-eq | = |
-ne | ≠ |
-lt | < |
-le | ≦ |
-gt | > |
-ge | ≧ |
文字列比較の場合は以下のような演算子が利用可能です。
利用可能な演算子 | 該当する比較演算子 |
---|---|
= | = |
!= | ≠ |
ファイルに関する判定の場合は以下のような演算子が利用可能です。
利用可能な演算子 | 該当する比較演算子 |
---|---|
-e | ファイルが存在するかどうか |
-f | 指定したファイルが通常のファイルかどうか |
-d | 指定したファイルがディレクトリかどうか |
-s | 指定したファイルのサイズがあるかどうか(空のファイルの場合1を返す) |
-r | ファイルに読み取り属性があるかどうか |
-w | ファイルに書き込み属性があるかどうか |
-x | ファイルに実行可能属性があるかどうか |
-nt | 左辺が右辺より新しいか(newer than) |
-ot | 左辺が右辺より古いか(older than) |
ここまでの情報でソースコードを見ていきます。 最初の部分は引数チェックです。
if [ $# -lt 1 ]; then
die "missing target file name"
fi
この部分は、条件判定を用いて引数の数をチェックしています。引数が1つ以上ない場合はシェルプログラムを継続することが出来ません。 そのため、die関数を呼び出して停止します。die関数については後程解説します。
以下の部分ではさらに引数を細かくチェックしています。 この部分は引数に指定したバックアップ対象のファイルが存在するかどうかをチェックしています。
if [ -e $1 ]; then
target_file_name=$1
else
die "target file ${1} that you specified does not exists."
fi
"[ -e $1 ]"の部分は第一引数で指定されたバックアップ対象のファイルが存在するかどうかをチェックして、存在すれば、変数target_file_nameに代入しています。 ファイルが存在しない場合は、シェルプログラムを継続することが出来ません。die関数を呼び出して停止します。
最後の部分はif文の入れ子になっています。
if [ $2 ]; then
if [ -e $2 ]; then
die "backup file ${2} already exists"
else
backup_file_name=$2
fi
else
backup_file_name=`date +%Y%m%d%H%M%S`.tgz
fi
外側のif文では$2引数が存在するかどうかだけを"if [ $2 ]"という簡単なチェックで調べています。 このif文では$2という引数が存在する場合([ $2 ]がtrueの場合)と存在しない場合で処理を分岐しています。
第2引数である$2が存在しなければ、以下の部分で日付形式のファイル名(変数backup_file_name)を指定します。
backup_file_name=`date +%Y%m%d%H%M%S`.tgz
この中の以下のコマンドは20140102030405のような日付+時刻の文字列を出力します。
date +%Y%m%d%H%M%S
結果、全体で変数backup_file_nameの中には20140102030405.tgzという文字列の値が格納されます。
第2引数である$2が存在した場合は、さらに過去に作成したバックアップファイルを上書きしてはいけないので、内側のif文の中ですでにファイルが存在しないかどうかをチェックします。 第2引数である$2で指定したファイルがすでに存在した場合([ -e $2 ]がtrueの場合)は、処理を停止します。