シェル・スクリプトのデバッグ・ツール、bashdb

 Bash Debugger Project(bashdb)を使うと、bashスクリプトにブレークポイントを設定したり変数を調べたりバックトレースしたり1行ずつ実行したりすることができる。bashdbは、C/C++デバッガー並みの技法でbashスクリプトをデバッグするツールだ。

 インストールされているbashがbashdbをサポートしているかどうかは、次のコマンドを実行してみればわかる。下に示したbashdbのプロンプトが表示されなければ、bashdbをインストールする必要がある。

$ bash --debugger -c "set|grep -i dbg"
...
bashdb<0>

 bashdbのパッケージはUbuntu Intrepidのリポジトリーにはあるが、openSUSE 11やFedora 9のリポジトリーにはない。試用マシンは64-bit Fedora 9マシンなので、通常のコマンド「./configure; make; sudo make install」を使ってソースからbashdbバージョン4.0-0.1をビルドした。

 Bash Debuggerを起動するときは「bash --debugger foo.sh」という構文を利用するか、「bashdb foo.sh」コマンドを実行する。I/Oリダイレクションで問題が発生する可能性がある場合を除き最初の起動方法が推奨されているので、以下の説明ではこの方法を用いることにする。このほか、dddEmacsバッファーから起動することもできる。

 bashdbのコマンドは、GNU debugger(gdb)とよく似ている。たとえば、スクリプトの次の行を実行するコマンドはstepまたはnextで、前者は関数の内部にまで入るが後者は入らない。バックトレースはbt、bashdbを抜けるときはquitまたはCtrl-D、変数を表示するなら「print $foo」という具合だ。しかし、gdbとは少し異なるところもあり、最後の例で変数に接頭辞$を付けるのもその一つ。また、bashdbのプロンプトで何も入力せずにEnterを押すと、直前のコマンドに関わらず、最後に実行したstepコマンドまたはnextコマンドのいずれかが実行されるところも異なる。

 シェル変数を確認したいときは、上に述べたようにprintコマンドを使うが、その際、変数名には接頭辞を付けなければならない($foo)。もう少し短い「 x foo」コマンドを使う方法もある。このコマンドはdeclareコマンドを使って表示するので、関数の定義を確認したいときにも使える。

 bashdbとデバッグ対象のスクリプトは同じbashシェルの中で動作する。そして、bashは名前空間という概念が希薄なため、bashdbの関数とシンボルはグローバルな名前空間の中に置かれることになり、したがってスクリプトからも見えることになる。そこで、bashdbはシンボルに_Dbg_という接頭辞を付けて区別する。このため、スクリプトでこの接頭辞を使ってはならない。さもないと、クラッシュする可能性がある。bashdbは環境変数も使うが、同様に、DBG_という接頭辞が付く。ほかに、BASH_という接頭辞を持つ標準のbash環境変数も使う。

 以下、bashdbの使い方を説明するが、その際、例として次に示すbashスクリプトを用いることにする。これは1つの数値nを引数として受け取り、n番目のフィボナッチ数を計算するスクリプトだ。

#!/bin/bash

version="0.01";

fibonacci() {
    n=${1:?If you want the nth fibonacci number, you must supply n as the first parameter.}
    if [ $n -le 1 ]; then
	echo $n
    else
	l=`fibonacci $((n-1))`
	r=`fibonacci $((n-2))`
	echo $((l + r))
    fi
}

for i in `seq 1 10`
do
  result=$(fibonacci $i)
  echo "i=$i result=$result"
done

 それでは、このスクリプトの動作をbashdbで調べてみよう。下に示したのは、スクリプトを1行ずつ実行していき、fibonacci関数の中に入って変数を調べている様子だ。見やすさのため、入力したテキストは太字にしてある。最初にバックトレース(bt)を実行しているが、その結果からスクリプトが3行目から始まっていることがわかる。この行では、version変数に値を設定している。次に、nextコマンドで次の行に進み、listコマンドで現在実行している行を表示している。次のnextコマンドに続いて、何も入力せずにEnterを押しているが、この場合これはnextコマンドを実行することになる。次に、examineコマンドの1文字ショートカットであるxを実行している。その結果から、このコマンドがdeclareを利用して変数を表示していることがわかる。このすぐ後で、同じ変数をprintコマンドを使って表示しているが、こちらはdeclareを使わない。次に、fibonacci関数にブレークポイントを設定し、continueでシェル・スクリプトの実行を再開させている。そして、fibonacci関数が呼び出されるとスクリプトの実行が止まる。そこで、nextで1行ずつ実行していき、変数を調べて終わっている。

$ bash --debugger ./fibonacci.sh
...
(/home/ben/testing/bashdb/fibonacci.sh:3):
3:	version="0.01";
bashdb<0> bt
->0 in file `./fibonacci.sh' at line 3
##1 main() called from file `./fibonacci.sh' at line 0
bashdb<1> next
(/home/ben/testing/bashdb/fibonacci.sh:16):
16:	for i in `seq 1 10`
bashdb<2> list
 16:==>for i in `seq 1 10`
 17:   do
 18:   result=$(fibonacci $i)
 19:   echo "i=$i result=$result"
 20:   done
bashdb<3> next
(/home/ben/testing/bashdb/fibonacci.sh:18):
18:	result=$(fibonacci $i)
bashdb<4>
(/home/ben/testing/bashdb/fibonacci.sh:19):
19:	echo "i=$i result=$result"
bashdb<5> x i result
declare -- i="1"
declare -- result=""

bashdb<7> print $i $result
1
bashdb<10> break fibonacci
Breakpoint 1 set in file /home/ben/testing/bashdb/fibonacci.sh, line 5.
bashdb<11> continue
Breakpoint 1 hit (1 times).
(/home/ben/testing/bashdb/fibonacci.sh:5):
5:	fibonacci() {
bashdb<(12)> next
(/home/ben/testing/bashdb/fibonacci.sh:6):
6:	n=${1:?If you want the nth fibonacci number, you must supply n as the first parameter.}
bashdb<(13)> next
(/home/ben/testing/bashdb/fibonacci.sh:7):
7:	if [ $n -le 1 ]; then
bashdb<(14)> x n
declare -- n="2"
bashdb<(15)> quit

 ここで、終わり近くのbashdbプロンプトの数字が丸括弧でくくられている点に注意。これは、サブシェルに入ったことを示している。この場合は、シェル関数の中に入ったことを意味する。

 次の例では、ウォッチポイントを使ってresult変数がいつどこで変更されるかを調べている。最初にnextコマンドを実行しているが、これはこうしないとウォッチが機能しなかったからだ。次に、ウォッチポイントを設定しcコマンドで実行を再開させている。そして、result変数の値が変化するところで実行が停止し、新旧の値が表示される。

(/home/ben/testing/bashdb/fibonacci.sh:3):
3:	version="0.01";
bashdb<0> next
(/home/ben/testing/bashdb/fibonacci.sh:16):
16:	for i in `seq 1 10`
bashdb<1> watch result
 0: ($result)==0 arith: 0
bashdb<2> c
Watchpoint 0: $result changed:
  old value: ''
  new value: '1'
(/home/ben/testing/bashdb/fibonacci.sh:19):
19:	echo "i=$i result=$result"
bashdb<3> c
i=1 result=1
i=2 result=1
Watchpoint 0: $result changed:
  old value: '1'
  new value: '2'
(/home/ben/testing/bashdb/fibonacci.sh:19):
19:	echo "i=$i result=$result"

 次の例では、引数の式が真になったとき実行を停止させるwatcheコマンドを使った。このコマンドの場合は、不可思議な「最初のnext」は不要だ。ここでは、初めの方のフィボナッチ数は飛ばしてresultが4を超えたときに停止するようウォッチを設定している。このコマンドは条件なしで使うこともできる。たとえば、watche resultとすると、result変数が変化したとき停止する。

$ bash --debugger ./fibonacci.sh
(/home/ben/testing/bashdb/fibonacci.sh:3):
3:	version="0.01";
bashdb<0> watche result > 4
 0: (result > 4)==0 arith: 1
bashdb<1> continue
i=1 result=1
i=2 result=1
i=3 result=2
i=4 result=3
Watchpoint 0: result > 4 changed:
  old value: '0'
  new value: '1'
(/home/ben/testing/bashdb/fibonacci.sh:19):
19:	echo "i=$i result=$result"

 シェル・スクリプトが思い通りに動かないときは、echo文やprintf文を順次挿入して不正な値や到達しないコード・パスを探したものだ。しかし、bashdbを利用すれば、変数をウォッチしたりブレークポイントを設定したりして、速やかにデバッグすることができる。

Ben Martin 10年以上にわたってファイルシステムに携わっている。博士課程を修了し、現在、libferris、ファイルシステム、検索ソリューションを中心にコンサルティングをしている。

Linux.com 原文(2008年11月24日)