【シェルスクリプト】特殊変数「$?」で終了ステータスを取得する方法

シェルスクリプトで、直前に実行したコマンドの終了ステータスを求める方法です。

終了ステータスを格納する特殊変数「$?」

特殊変数「$?」を使うと、直前に実行したコマンドの終了ステータス(終了値)を取得することができます。

一般的には終了ステータスは下記のようになります。

  • 0 成功(正常終了)
  • 0以外 失敗(異常終了)

「$?」で終了ステータスを取得する例

終了ステータス 0(成功)の例

「cp」コマンドを実行して正常にコピーに成功した場合は、終了ステータスが「0」となります。

echoで「$?」の中身を表示させると、取得した終了ステータスである「0」が表示されます。

$ cp test.txt test_01.txt 
$ echo $?
0

終了ステータス 1(失敗)の例

「cp」コマンドを実行してコピーに失敗した場合、終了ステータスが「1」となります。

「echo」で「$?」の中身を表示させると、取得した終了ステータスである「1」が表示されます。

$ cp test01.txt test_02.txt 
cp: cannot stat 'test01.txt': No such file or directory
$ echo $?
1

終了ステータスについて

終了ステータスには0〜255までの整数を設定することが出来て、プログラムの作成者が終了値に決めることができますが、慣例として成功した場合は「0」を設定し、失敗した場合は「1〜255」(0以外)を設定します。

失敗した内容により異なる終了ステータスを返すようにすると、失敗原因の判別や失敗の内容によって異なる処理を行ったりということもできます。

今回は「ls」コマンドを例にとって見てみます。

「ls」コマンドの「man」を確認してみると終了ステータスについて下記のような説明が記述されています。

Exit status:
0 if OK,
1 if minor problems (e.g., cannot access subdirectory),
2 if serious trouble (e.g., cannot access command-line argument).

終了ステータスが3つあって、失敗についての終了ステータスが「1」「2」と2つ設定されていることがわかります。

成功の例 終了ステータス0

「ls」コマンドが成功すると終了ステータスとして「0」が「$?」に格納されます。

$ ls
test.txt  test_01.txt
$ echo $?
0

失敗の例 終了ステータス2

存在しないファイルを指定して「ls」コマンドを実行すると、当然ですが失敗します。

この場合は終了ステータスとして「2」が「$?」に格納されます。

$ ls test00.txt
ls: cannot access 'test00.txt': No such file or directory
$ echo $?
2

失敗の例 終了ステータス1

一部のサブディレクトリにアクセスできないといった場合、終了ステータスとして「1」が「$?」に格納されます。

$ ls -lR ./
./:
total 4
-rw-rw-r-- 1 tamohiko tamohiko    0 Jun  6 21:34 test.txt
-rw-rw-r-- 1 tamohiko tamohiko    0 Jun  6 21:35 test_01.txt
drwx------ 2 root     root     4096 Jun  6 22:40 test_dir
ls: cannot open directory './test_dir': Permission denied
$ echo $?
1

Bashスクリプトで終了ステータス使った条件分岐

Bashのシェルスクリプトで終了ステータスで条件分岐させたい場合、終了ステータスを一度別の変数に格納する必要があります。

例として、下記のように引数として入力したデータに「ls」コマンドを実行した結果と、終了ステータスを表示させる「exitcode_01.sh」というシェルスクリプトを作成しました。

ここで大事な部分は「STATUS=$?」で、「ls」コマンドの終了ステータスを一度別の変数に格納しています。

$ cat ./exitcode_01.sh 
#!/bin/bash

ls -Rl $1

STATUS=$?

if [ ${STATUS} -eq 0 ]; then
  echo "Command Sucess. Exit Status = 0"
elif [ ${STATUS} -eq 1 ]; then
  echo "Command Failure. Exit Status = 1"
elif [ ${STATUS} -eq 2 ]; then
  echo "Command Failure. Exit Status = 2"
else
  echo "Command Failure. Exit Status = ${STATUS}"
fi

動作試験

「ls」コマンド成功時は「Command Sucess. Exit Status = 0」と表示されます。

$ ./exitcode_01.sh test.txt
-rw-rw-r-- 1 tamohiko tamohiko 21 Dec  3  2022 test.txt
Command Sucess. Exit Status = 0

一部のでサブディレクトリの情報がパーミッションの関係で取得できない場合、「ls」コマンドの終了ステータスは「1」となるので「Command Failure. Exit Status = 1」と表示されます。

$ ./exitcode_01.sh test    
test:
total 4
-rw-rw-r-- 1 tamohiko tamohiko    0 Jun  6 21:34 test.txt
-rw-rw-r-- 1 tamohiko tamohiko    0 Jun  6 21:35 test_01.txt
drwx------ 2 root     root     4096 Jun  6 22:40 test_dir
ls: cannot open directory 'test/test_dir': Permission denied
Command Failure. Exit Status = 1

存在しないデータを「ls」コマンドに渡した場合、「ls」の終了ステータスが「2」となりますので、「Command Failure. Exit Status = 2」と表示されます。

$ ./exitcode_01.sh test000.txt
ls: cannot access 'test000.txt': No such file or directory
Command Failure. Exit Status = 2

条件分岐が失敗するスクリプトの例

以下は、終了ステータスを格納した「$?」を使って条件分岐をさせようとして失敗した、私が最初に作成したスクリプトになります。

「ls」コマンドの終了ステータスを「$?」から別の変数に格納していないため、lsコマンドが失敗すると最初の「if [ $?-eq 0 ]」部分での判別の結果終了ステータスが「1」となり、「$?」の内容が「1」に上書きされてしまいます。

これは「[ ]」がtestコマンドの略式であるためで、直前に実行された「test」コマンドの終了ステータスが「$?」に格納されてしまうためです。

そのため、「ls」コマンドの終了ステータスが「2」であったとしても、「$?」には「test」コマンドの終了ステータスである「1」が格納されているので、必ず「$?」の中身は「1」となり失敗の全てで「Command Failure. Exit Status = 1」と表示されてしまいます。

$ cat exitcode_00.sh 
#!/bin/bash

ls -Rl $1

if [ $? -eq 0 ]; then
  echo "Command Sucess. Exit Status = 0"
elif [ $? -eq 1 ]; then
  echo "Command Failure. Exit Status = 1"
elif [ $? -eq 2 ]; then
  echo "Command Failure. Exit Status = 2"
else
  echo "Command Failure. Exit Status = $?"
fi

動作試験

「ls」コマンドが正常終了したときは終了ステータス「0」が帰ってくるので問題なく動作しています。

$ ./exitcode_00.sh test.txt
-rw-rw-r-- 1 tamohiko tamohiko 21 Dec  3  2022 test.txt
Command Sucess. Exit Status = 0

「ls」コマンドでサブディレクトリの情報を取得できない場合終了ステータスが「1」となるので、こちらも一見問題なく動作しているように見えていますが、これは「ls」コマンドの終了ステータスではなく「[]」によるtest結果の終了コードが「1」でだったので、運良く(悪く?)意図した動作と同じ結果が帰ってきているだけでした。

$ ./exitcode_00.sh test    
test:
total 4
-rw-rw-r-- 1 tamohiko tamohiko    0 Jun  6 21:34 test.txt
-rw-rw-r-- 1 tamohiko tamohiko    0 Jun  6 21:35 test_01.txt
drwx------ 2 root     root     4096 Jun  6 22:40 test_dir
ls: cannot open directory 'test/test_dir': Permission denied
Command Failure. Exit Status = 1

存在しないデータを「ls」コマンドに渡した場合は終了ステータスが「2」となるのに、終了ステータスが「1」の場合の処理を行ってしまっています。

$ ./exitcode_00.sh test000.txt 
ls: cannot access 'test000.txt': No such file or directory
Command Failure. Exit Status = 1
-xvオプションで動作状況をトレース

スクリプトの実行状態を「-xv」オプションでトレースしてみると、最初は「$?」に終了ステータス「2」が格納されていて、その後「if [ $? -eq 0 ]」の「test」コマンド実行結果によって、「$?」の内容が「1」に上書きされていることがわかります。

$ bash -xv ./exitcode_00.sh test000.txt
#!/bin/bash

ls -Rl $1
+ ls -Rl test000.txt
ls: cannot access 'test000.txt': No such file or directory

if [ $? -eq 0 ]; then
  echo "Command Sucess. Exit Status = 0"
elif [ $? -eq 1 ]; then
  echo "Command Failure. Exit Status = 1"
elif [ $? -eq 2 ]; then
  echo "Command Failure. Exit Status = 2"
else
  echo "Command Failure. Exit Status = $?"
fi
+ '[' 2 -eq 0 ']'    # 「$?」に「2」が格納されている
+ '[' 1 -eq 1 ']'    # 「[ ]」の実行結果で「$?」の内容が「1」に上書きされている
+ echo 'Command Failure. Exit Status = 1'
Command Failure. Exit Status = 1

コメント

タイトルとURLをコピーしました