シェル・スクリプトのデバッグ・ツール、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リダイレクションで問題が発生する可能性がある場合を除き最初の起動方法が推奨されているので、以下の説明ではこの方法を用いることにする。このほか、dddやEmacsバッファーから起動することもできる。
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、ファイルシステム、検索ソリューションを中心にコンサルティングをしている。