シェルスクリプトの基礎 - 終了ステータス
概要
コマンド終了時には、終了ステータスと呼ばれるコマンドの成否を表す数値が特殊変数$?
に自動で設定される。
各コマンドにより異なるが、コマンド成功時は0、失敗時は1(コマンドやエラーの種類によっては0以外)が設定される。
直前に実行したコマンドの成否は、以下のように、特殊変数$?に設定されている値で確認できる。
<コマンド> echo $?
終了ステータスの設定
シェルスクリプトでは、exitコマンドに指定したパラメータ(0または1~255の正の整数)が終了ステータスとなる。
正常に終了した時はexit 0
、異常終了時はexit 1
で終了することが慣例である。
関数も同様に、returnコマンドに指定したパラメータが終了ステータスとなる。
exit <数値>
return <数値>
exitコマンドやreturnコマンドに終了ステータスを指定することで、任意の終了ステータスでシェルスクリプトおよび関数を終了することができる。
exitコマンドは省略可能であるが、省略する場合は、シェルスクリプト内で最後に実行したコマンドの終了ステータスが、
シェルスクリプトの終了ステータスとなる。
また、returnコマンドも同様となる。
cat <存在しないファイル名> # catコマンドが失敗する場合、終了ステータスは1となる echo $? # 出力 1
touch <新規ファイル名> # catコマンドが成功する場合、終了ステータスが0となる cat <新規ファイル名> echo $? # 出力 0
以下の例では、exitコマンドを使用して終了ステータスを設定している。
#!/bin/bash
# カレントディレクトリにあるfooディレクトリの存在を確認
if [ -d ./foo ]; then
echo "ディレクトリが存在する"
exit 0
else
echo "ディレクトリが存在しない"
exit 1
fi
# 出力
# ファイルが存在する場合
echo $?
0
# ディレクトリが存在しない場合
echo $?
1
終了ステータスを判定してコマンドを実行する
コマンドが成功した場合のみ次のコマンドを実行する
以下のように、&&でコマンドを繋ぐ場合、直前のコマンドが成功した場合のみ、次のコマンドが実行される。
コマンドが&&で結合される場合、シェルはcommand1の終了ステータスを判断して、0(成功)である場合のみcommand2を実行する。
さらに、複数のコマンドが結合される場合も、直前のコマンドが成功した場合のみ、次のコマンドが実行される。
command1 && command2 command1 && command2 && command3
直前のコマンドが失敗していると困る処理において、&&を使用する場合、直前のコマンドが成功していることが保証されるため、
連続して2つのコマンドを実行するよりも確実性の高い処理になる。
ls hogehoge && echo "exists."
コマンドが失敗した場合のみ次のコマンドを実行する
以下のように、||でコマンドを繋ぐ場合、直前のコマンドが失敗した場合のみ、次のコマンドが実行される。
コマンドが||で結合されると場合、シェルはcommand1の終了ステータスを判断して、0以外(失敗)であった場合のみcommand2を実行する。
さらに、複数のコマンドが結合される場合も、直前のコマンドが失敗した場合のみ、次のコマンドが実行される。
command1 || command2 command1 || command2 || command3
command1が失敗した場合のみcommand2を試すといった処理を行う場合、この||を使用する。
ls fugafuga 2>/dev/null || echo "not exists."
rm -f
の終了ステータス
rm -f
コマンド(強制削除)の終了ステータスは、常に0となるため、&&または||を使用してコマンドを連結しても期待する結果にはならない。
例えば、ファイルを削除できなかった場合のみ、あるコマンドを実行するといった処理は、終了ステータスの判定ではできない。
# -fオプションを付加して実行する場合、ファイルが存在しなくともrmコマンドは成功する # ただし、rm -fを使用する場合でも、/tmp等のスティッキービットが設定されているディレクトリで、 # 他のユーザのファイルを削除しようとした場合には、終了ステータスが1となる。 rm -f foo echo $? 0
このように、rm -f
の終了ステータスは判断が難しいため、強制削除の終了ステータス判定処理は避けること。
終了ステータスの応用
入力値の判別(数値または文字列)
キーボードから入力する値が、数値または文字列かを判別する場合、exprコマンドとその終了ステータスを使用する。
exprコマンド(四則演算などに使用するコマンド)は、以下のような終了ステータスである。
- 数値同士で演算を行った場合は、0
- 数値同士でも計算結果が0になる場合は、1
- 数値以外で演算を行った場合(計算できない場合)は、2以上
つまり、数値とキーボードからの入力値をexprコマンドで演算を行い、
終了ステータスが0または1ならば入力値は数値、2以上ならば入力値は数値以外の文字列と判断することができる。
以下の例では、exprコマンドと終了ステータスを使用して入力値の判定を行っている。
#!/bin/sh
# キーボードから入力(-n を付けると改行なしで出力する)
read -p "数字を入力してください > " KEY
# 入力値に1を加算して、その終了ステータスを判定する
expr 1 + $KEY >/dev/null 2>&1
case $? in
0 | 1) echo "[\$?=$?] 数字が入力されました > $KEY" ;;
*) echo "[\$?=$?] 数字以外が入力されました > $KEY" ;;
esac
exit 0
上記のシェルスクリプトの実行結果は、以下の通りとなる。
数字を入力してください >1 [$?=0] 数字が入力されました >1 #↑expr コマンドが成功するので、終了ステータスは「0」になる。
$ ./input_check.sh 数字を入力してください >-1 [$?=1] 数字が入力されました >-1
- ↑expr コマンドは成功するが計算結果が0となるので、終了ステータスは「1」になる。
$ ./input_check.sh 数字を入力してください >hoge [$?=3] 数字以外が入力されました >hoge
- ↑計算できないため expr コマンドが失敗し、終了ステータスは「3」になる。
パイプ処理の終了ステータスの取得
パイプ処理を行う場合、特殊変数$?に設定される値は、パイプ処理の最後に実行するコマンドの終了ステータスとなる。
以下のような例を考える。
exit 0 | exit 1 | exit 2 echo $? # 出力 2
この時、最後のコマンドの終了ステータスではなく、パイプ処理の先頭または途中で実行するコマンドの終了ステータスを取得したい場合がある。
この場合、特殊変数$PIPESTATUS(配列)を参照することで、パイプ処理にて実行された各コマンドの終了ステータスを取得することができる。
(ただし、bashのみ)
以下の例では、全要素(全終了ステータス)を参照しているが、個別に参照することもできる。
詳細は、シェルスクリプトの基礎 - 配列を参照すること。
exit 0 | exit 1 | exit 2 echo ${PIPESTATUS[@]} # $PIPESTATUS配列において、@を指定して全要素を出力している # 出力 0 1 2