終了ステータス
終了ステータスとは?
コマンド終了時には「終了ステータス (exit-status)」と呼ばれるコマンドの成否を表す数値が、特殊変数 $? に自動で設定される。
各コマンドにより異なるが、一般的には、
コマンド成功時には「0」
失敗時には「1」(コマンドやエラーの種類によっては 0 以外)
が設定される。
code:bash
command
echo $?
→ 直前に実行したコマンドの成否は、特殊変数 $? に設定されている値で確認する。
終了ステータスを設定
シェルスクリプトでは exit コマンドに指定したパラメータ (0 もしくは 1 ~ 255 の正の整数値のみ可) が、そのシェルの終了ステータスとなる。シェルスクリプトにおいても正常終了時は exit 0 で、異常終了時には exit 1 で終了するようにするのが慣例である。
関数も同様に return コマンドに指定したパラメータが終了ステータスとなる。
code:bash
exit status
return status
→ シェルスクリプトの終了ステータスを設定するには、exit コマンドに 0 もしくは 1 ~ 255 の正の整数値を指定する。
exit コマンド、関数ならば return コマンドに終了ステータスとなる値を指定することで、任意の終了ステータスでシェルスクリプトおよび関数を終了することができる。
exit コマンドは省略可能であるが、省略された場合はシェルスクリプト内で最後に実行されたコマンドの終了ステータスがそのシェルスクリプトの終了ステータスとなる。関数においても、exit コマンドの代わりに return コマンドを使用すること以外は同様である。
code:terminal
$ cat hogehoge
cat: hogehoge: そのようなファイルやディレクトリはありません
$ echo $?
1
$ touch hogehoge
$ cat hogehoge
$ echo $?
0
#↑touch コマンドで空のファイルを作成したので、今度は cat コマンドが成功し、終了ステータスが 0 になった。 シェルスクリプトに exit コマンドを使用して、終了ステータスを設定するシェルスクリプト (exit_status.sh) を作成してみる。
code:bash
# カレントディレクトリ内に foo ディレクトリが存在するか?
echo "ディレクトリが存在します。"
exit 0
else
echo "ディレクトリが存在しません。"
exit 1
fi
このシェルスクリプト exit_status.sh の実行結果は、以下のとおりとなる。
code:terminal
$ ls -d ./foo
ls: ./foo: そのようなファイルやディレクトリはありません
$ ./exit_status.sh
ディレクトリが存在しません。
$ echo $?
1
#↑foo ディレクトリが存在しないので、シェルスクリプトは 1 で終了した。 $ mkdir ./foo
$ ls -d ./foo
./foo
$ ./exit_status.sh
ディレクトリが存在します。
$ echo $?
0
終了ステータスを判定してコマンドを実行する
コマンドが成功した場合のみ次のコマンドを実行する
code:bash
command1 && command2
command1 && command2 && command3
→ && でコマンドをつなげると、直前のコマンドが成功した場合のみ次のコマンドが実行される。
コマンドが && で結合されていると、シェルは command1 の終了ステータスを判断し、「0」(成功)であった場合のみ command2 を実行する。
さらに複数のコマンドが結合されている場合も、直前のコマンドが成功した場合のみ、次のコマンドが実行される。
直前のコマンドが失敗していると困る処理 (例えば、1 つ目のコマンドが失敗すると、2 つ目のコマンドが確実に失敗するような処理) には、この && を使用すると直前のコマンドが成功していることが保証されるので、単純に連続して 2 つのコマンドを実行するよりも確実性の高い処理になる。
code:terminal
$ ls hogehoge
ls: hogehoge: そのようなファイルやディレクトリはありません
$ ls hogehoge && echo "exists."
ls: hogehoge: そのようなファイルやディレクトリはありません
#↑存在しないので ls コマンドは失敗し、&& で結合した echo コマンドは実行されない。 $ touch hogehoge
$ ls hogehoge && echo "exists."
hogehoge
exists.
#↑今度は ls コマンドが成功するので、echo コマンドが実行される。 コマンドが失敗した場合のみ次のコマンドを実行する
code:bash
command1 || command2
command1 || command2 || command3
→ || でコマンドをつなげると、直前のコマンドが失敗した場合のみ次のコマンドが実行される。
コマンドが || で結合されていると、シェルは command1 の終了ステータスを判断し、「0」以外(失敗)であった場合のみ command2 を実行する。
さらに複数のコマンドが結合されている場合も、直前のコマンドが失敗した場合のみ、次のコマンドが実行される。
もし command1 が失敗したら command2 を試したい、といったような処理を行う場合は、この || を使用する。
code:terminal
$ ls fugafuga
ls: fugafuga: そのようなファイルやディレクトリはありません
$ ls fugafuga 2>/dev/null || echo "not exists."
not exists.
$ touch fugafuga
$ ls fugafuga 2>/dev/null || echo "not exists."
fugafuga
「 rm -f 」の終了ステータス
rm -f (強制削除) の終了ステータスは常に「0」となるため、&& や || を使用してコマンドを連結しても狙い通りの結果にはならない。
例えば「ファイルを削除できなかった場合のみ、あるコマンドを実行する」といった処理は、終了ステータスの判定ではできない。
code:terminal
$ ls foo
ls: foo: そのようなファイルやディレクトリはありません
$ rm foo
rm: cannot remove `foo': そのようなファイルやディレクトリはありません
$ echo $?
1
$ rm -f foo
$ echo $?
0
#↑-f オプション付きで実行すると、ファイルが存在しなくとも rm コマンドは成功。 ただし rm -f を使用した場合でも /tmp などスティッキービットが設定されているディレクトリで、他のユーザのファイルを削除しようとした場合には、終了ステータスが「1」になる。 このように rm -f の終了ステータスは当てにならないので、強制削除の終了ステータス判定処理は避けること。
終了ステータスの応用
入力値が数値か文字列かを判別する
キーボードから入力された値が、数値かそれとも数字以外の文字列かを判別したい場合は、expr コマンドとその終了ステータスを使用する。
expr コマンド (四則演算などに使用するコマンド) は、
数値同士で演算を行った場合は「0」
数値同士でも計算結果が 0 になる場合は「1」
数値以外で演算を行った場合 (計算できない場合) は「2 以上」
が終了ステータスとなる。
つまり適当な数値とキーボードからの入力値とを expr コマンドで演算を行い、
終了ステータスが「0」か「1」ならば入力値は数値、
「2」以上ならば入力値は数値以外の文字列、
と判断することができる。
この expr コマンドと終了ステータスを使用した、入力値の判定を行うシェルスクリプト (input_check.sh) を作成してみる。
code:bash
# キーボードから入力(-n を付けると改行なしで出力する)
echo -n "数字を入力してください >"
read KEY
# 入力値に1をプラスし、その終了ステータスを判定する。
expr 1 + $KEY >/dev/null 2>&1
case $? in
0 | 1 ) echo "\$?=$? 数字が入力されました >$KEY" ;; * ) echo "\$?=$? 数字以外が入力されました >$KEY" ;; esac
exit 0
このシェルスクリプト input_check.sh の実行結果は、以下のとおりとなる。
code:terminal
$ ./input_check.sh
数字を入力してください >1
#↑expr コマンドが成功するので、終了ステータスは「0」になる。 $ ./input_check.sh
数字を入力してください >-1
#↑expr コマンドは成功するが計算結果が0となるので、終了ステータスは「1」になる。 $ ./input_check.sh
数字を入力してください >hoge
パイプ処理の終了ステータスを取得する
パイプ処理を行った場合、特殊変数 $? に設定される値は当然ながら、パイプ処理の一番最後に実行されたコマンドの終了ステータスとなる。
例えば次のような場合を考える。
code:terminal
$ exit 0 | exit 1 | exit 2
$ echo $?
2
#↑$? には最後のコマンドの終了ステータスが設定されている。 このようなパイプ処理において、最後のコマンドの終了ステータスではなく、パイプ処理の先頭、もしくは途中で実行しているコマンドの終了ステータスを取得したい場面が出てくると思う。
code:terminal
$ exit 0 | exit 1 | exit 2
0 1 2
ここでは全要素 (全終了ステータス) を参照しているが、もちろん個別に参照することも可能である。詳しくは、配列の使用方法のページ「配列を使用する」を参照してほしい。