※このエントリーは、developerWorks : AIX and UNIXの「Speaking UNIX: More shell scripting techniques」を翻訳したエントリーのPart 4です。
【訳注】 「シェルスクリプト作成のテクニック Part 3」の続きのエントリーです。
デバッグする
スクリプトを書き終えたら、プログラムを動かしてみる時間です。ところが、スクリプトを実行したところ、予期しないエラーが表示されてしまいました。原因はなんでしょう?この世の中に完全無欠な人なんていません。スクラッチからスクリプトを書き始め、エラーが出なくなるようにするまでに多くの時間と試行錯誤を要します。たいていの場合、文字が抜けていたり、間違った文字になっていたりしているのが原因で、どうしても見逃してしまうようです。それでもご安心下さい。AIXを含むUNIXやLinuxのシェルには、デバッグを助けてくれる機能があります。
例えば、Listing 10でご紹介するmake_errorsという名前のシェルスクリプトは、たった今書き上がり、実行を待っている状態です。
Listing 10: エラーを含むスクリプトの例
#!/bin/bash
_X=1
while [[ ${_X} -le 10 ]]
do
[[ ${_X} -lt 5 ]] && echo "X is less than 5!
_Y=`expr ${_X) + 1`
if [[ ${_Y} -eq 6 ]]
echo "Y is now equal to ${_Y}"
fi
_X=${_Y}
done
しかし、最初にこのスクリプトを実行してむると、以下のようなエラーが表示されます。
# ./make_errors ./make_errors: line 11: unexpected EOF while looking for matching `"' ./make_errors: line 16: syntax error: unexpected end of file
あなたがすでに使っている可能性があり、それにも関わらずあまり知られていない優れたデバッグツールがVimです。Vimは強力なテキストエディターであると同時に、デバッグでも非常に役に立ってくれます。もし、あなたが.exrcや.vimrcファイルを設定し、特定のエラー状況をカラーで表示してくれるようにしているならば、Vimは図1で表示されるようにあなたの仕事の大部分をやってくれるでしょう。
図1: Vimでデバッグする
最初のエラー「line 11: unexpected EOF while looking for matching `"'」は、11行目で何かが起こっていると言っていますが、その行を見てみても、何が悪いかよく分かりません。それでは6行目を見てみましょう。ダブルクオーテーションマーク(")がechoされている文字列の最後から抜けています。これはデバッグの際に実際にスクリプトを見ないといけないという典型的な例です。エラーとして表示される列が必ずしも実際のエラー発生行とは限りません。11行目がエラー発生行として表示されているのは、6行目でダブルクォーテーションで囲まれ始めて、11行目まで閉じられなかったからです。エラーを修正するには、6行目の最後にダブルクオーテーションを追加します。
それとは別にエラーがあると表示されているのは8行目です。ここでは、変数_Xの閉じる丸括弧「)」が赤くハイライトされています。ここではVimがあなたの代わりに何が悪いか表示してくれました。変数_Xは波括弧「{」で始まっているのに正しく閉じられていません。「)」から「}」に変更しておきましょう。
今のところ2つのエラーを修正するのによい仕事をしてくれています。それではスクリプトを再実行してみましょう。
./make_errors: line 12: syntax error near unexpected token `fi' ./make_errors: line 12: ` fi'
別のエラーがあるようです。エラーは12行目に問題があると言っていますが、そこにはif文を閉じるfiしかありません。何が駄目なんでしょうか?前のエラーの時はどんなだったか思い出して下さい。必ずしもすべてのエラーがシェルが問題があると指摘した行にあるわけではありません。シェルは単純にエラーが発生したと言っているだけで、実際のエラー原因はシェルが失敗を報告した行以前にある可能性があります。この小さなスクリプトではほぼ間違いなく、if文自体に問題があると思われます。シェルスクリプトの基本ロジックに立ち戻り、if文がifとthenとfiから構成されていることを思い出しましょう。それでは見直してみましょう。どうやらthenが抜けているようです。単純にthenをスクリプトに追加するだけです。それが完了すると、Listing 11のようなスクリプトになります。
Listing 11: Listing 10のスクリプトを修正したもの
#!/bin/bash
_X=1
while [[ ${_X} -le 10 ]]
do
[[ ${_X} -lt 5 ]] && echo "X is less than 5!"
_Y=`expr ${_X} + 1`
if [[ ${_Y} -eq 6 ]]
then
echo "Y is now equal to ${_Y}"
fi
_X=${_Y}
done
それではもう一度スクリプトを実行してみましょう。
# ./make_errors X is less than 5! X is less than 5! X is less than 5! X is less than 5! Y is now equal to 6
おめでとうございます!スクリプトは想定通りに動くようになりました。
set -xオプション
しかしながら、シェルスクリプトのトラブルシューティングの方法は上記の例のように単純にはいかないことがあります。予想外の失敗が発生し、壁に頭を叩きつけても、失敗の原因が分からない時、最終手段として銃を取り出すしかないのでしょうか。kshとbash、それ以外の最新のシェルにはsetコマンドに-xオプションが含まれています。このset -xオプションを使うことにより、展開や評価されるすべてのコマンドが標準出力に表示されるようになります。評価されたコードを標準出力に出力する際、set -xはPS4変数の値を使用し、すべての行が出力されているかのように表示します。大量のテキストが出力されるので、辛抱強く内容を追いかけないといけないことは覚えておいて下さい。
以前取り上げたスクリプト例のループカウント数を少し減らし、set -xをスクリプトの冒頭に追加し、コメントも付けてみました。その後、これを実行してみます。Listing 12が修正したスクリプトです。
Listing 12: set -xの例
#!/bin/bash
set -x
# loop through and display some test statements
_X=1
while [[ ${_X} -le 4 ]]
do
[[ ${_X} -lt 2 ]] && echo "X is less than 2!
_Y=`expr ${_X} + 1`
if [[ ${_Y} -eq 3 ]]
then
echo "Y is now equal to ${_Y}"
fi
_X=${_Y}
done
スクリプトを実行する前に、PS4変数の値を標準出力に表示させたいものに変更します。
# export PS4="DEBUG => "
次に、Listing 13のようにおそらく有益であろう情報の爆撃を自分自身に向かって送り付けてあげましょう。
Listing 13: set -xの出力例
# ./make_errors DEBUG => _X=1 DEBUG => [[ 1 -le 4 ]] DEBUG => [[ 1 -lt 2 ]] DEBUG => echo 'X is less than 2!' X is less than 2! DDEBUG => expr 1 + 1 DEBUG => _Y=2 DEBUG => [[ 2 -eq 3 ]] DEBUG => _X=2 DEBUG => [[ 2 -le 4 ]] DEBUG => [[ 2 -lt 2 ]] DDEBUG => expr 2 + 1 DEBUG => _Y=3 DEBUG => [[ 3 -eq 3 ]] DEBUG => echo 'Y is now equal to 3' Y is now equal to 3 DEBUG => _X=3 DEBUG => [[ 3 -le 4 ]] DEBUG => [[ 3 -lt 2 ]] DDEBUG => expr 3 + 1 DEBUG => _Y=4 DEBUG => [[ 4 -eq 3 ]] DEBUG => _X=4 DEBUG => [[ 4 -le 4 ]] DEBUG => [[ 4 -lt 2 ]] DDEBUG => expr 4 + 1 DEBUG => _Y=5 DEBUG => [[ 5 -eq 3 ]] DEBUG => _X=5 DEBUG => [[ 5 -le 4 ]]
ご覧の通り、大量の情報が表示されます。すべてのコマンドが評価され、実行されています。また、デバッグ情報ではコメント行の内容が出力されていないことにも気付くはずです。これはコメント行の文字列といのは評価はされるけど、実行されていないからです。ありがたいことに、最初に修正して以降もスクリプトには問題はないようです。
一つだけ覚えておいて欲しいのは、set -xを使用した際、そのスクリプトに内部関数がある場合に、set -xオプションは子供の関数にも引き継がれるということです。しかし、set -xが内部関数にしか含まれない場合、その内部関数で実行されるコードとその子供の関数にしかデバッグオプションは引き継がれず、親のスクリプト部分には影響を及ぼしません。そのため、子供の関数が呼び出したルーチンが何をしているのか分からなくなります。
結び
私たちはシェルスクリプトであれ、Cであれ、Javaであれ、その他の言語であれ、プログラミング技術を向上させようとしています。コードは簡潔にし、明快かつ柔軟性を持ち、文書化を怠らないという基本ルールを守っていれば、あなたが学んだデバッグ手法から学ぶことがほとんどないような優れたシェルスクリプトを書けるようになるはずです。頑張ってください!
