harry’s memorandum

おれおれメモ

たぶん使わないであろうBashのデバッグ方法

http://d.hatena.ne.jp/dharry/20101121/1290273723
とりあえずBash好きなので、前に覚えたことの復習も兼ねてもう一回書いておきます。

setでデバッグ

’-x’, ’-v’ デバッグは基本ですよね。オプションつけて実行するとスクリプトがストリーキングになります。

$ cat foo.sh
for x in `seq 2`; do
  echo $x
done
$ bash -x foo.sh
++ seq 2
+ echo 1
1
+ echo 2
2

でもストリーキング愛好家の中には一部だけ露出したいという人もいます。そんなフェティシズムをお持ちでしたら内部で set をすれば OK です。

$ cat foo.sh
[ ${1}x = "-dx" ] && \
set -x
for x in `seq 2`; do
  echo $x
done
set +x
$ ./foo.sh
1
2
$ ./foo -d
++ seq 2
+ for x in '`seq 2`'
+ echo 1
1
+ for x in '`seq 2`'
+ echo 2
2
+ set +x

BASH_COMMANDをtrap

Bashには色々な疑似シグナルがあります。

疑似シグナル タイミング
EXIT シェルがスクリプトを終了した
ERR コマンド、シェル関数から0ではない終了ステータスが返された
DEBUG シェルが文を実行した
RETURN source または . で実行されたシェル関数/スクリプトが終了した

またBashには予約されたシェル変数があります。そのひとつが "BASH_COMMAND"です。 *1 BASH_COMMANDは実行しようとしているコマンドが変数に入っています。

BASH_COMMAND
The command currently being executed or about to be executed, unless the shell is executing a command as the result of a trap, in which case it is the command executing at the time of the trap.

http://linux.die.net/man/1/bash

DEBUGシグナルを使うとシェルが文を実行するたびにtrapできるので、スクリプトはヌーディストのようになります。
以下コードはスクリプトの特定の箇所をステップ実行します。trap - DEBUG とすればステップ実行の設定は解除されます。

$ cat foo.sh
if [ ${1}x = "-dx" ]; then
  set -x
  trap 'read -p "$0($LINENO) $BASH_COMMAND"' DEBUG
fi

while :
do
  dice=$((${RANDOM}%6+1))
  case  $dice in
    1) echo one;;
    2) echo two;;
    3) echo three;;
    4) echo four;;
    5) echo five;;
    6) echo six; break;;
  esac
done
trap - DEBUG

echo ok

$ ./foo.sh -d
++ trap 'read -p "$0($LINENO) $BASH_COMMAND"' DEBUG
+++ read -p './foo.sh(6) :'
./foo.sh(6) :
++ :
+++ read -p './foo.sh(8) dice=$((${RANDOM}%6+1))'
./foo.sh(8) dice=$((${RANDOM}%6+1))
++ dice=6
++ case $dice in
+++ read -p './foo.sh(9) case $dice in '
./foo.sh(9) case $dice in
+++ read -p './foo.sh(15) echo six'
./foo.sh(15) echo six
++ echo six
six
+++ read -p './foo.sh(15) break'
./foo.sh(15) break
++ break
+++ read -p './foo.sh(18) trap '\'''\'' DEBUG'
./foo.sh(18) trap '' DEBUG
++ trap '' DEBUG
++ echo ok
ok

bashdb

ステップ実行ができるのなら gdb ぽく解剖もできるんじゃね?と。案の定 bashdb というgdb風のデバッガがあったりします。
まずはインストール。パッケージがあればパッケージマネージャでインストール。

$ sudo apt-get install bashdb

なければソースからビルドします。 ここからダウンロード (http://sourceforge.net/projects/bashdb/files/)

$ tar xvzf bashdb-3.1-0.09.tar.gz
$ cd bashdb-3.1-0.09
$ ./configure
$ make
$ make install

見事にステップ実行できます。マジに素晴らしいです。

$ bashdb foo.sh
Bourne-Again Shell Debugger, release bash-3.1-0.09
Copyright 2002, 2003, 2004, 2006, 2007 Rocky Bernstein
This is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.

(/root/foo.sh:1):
1:      if [ ${1}x = "-dx" ]; then
bashdb<0> s
(/root/foo.sh:6):
6:      while :
bashdb<1> s
(/root/foo.sh:8):
8:      dice=$((${RANDOM}%6+1))

bashdb は bash で書かれています。

$ file /usr/local/bin/bashdb
/usr/local/bin/bashdb: Bourne-Again shell script text executable

もちろん BASH_COMMAND を trap しています。

$ grep BASH_COMMAND /usr/local/share/bashdb/*
/usr/local/share/bashdb/bashdb-main.inc:trap '_Dbg_debug_trap_handler 0 "$BASH_COMMAND" "$@"' DEBUG
/usr/local/share/bashdb/bashdb-trace:      trap '_Dbg_debug_trap_handler 0 "$BASH_COMMAND" "$@"' DEBUG
/usr/local/share/bashdb/bashdb-trace:      trap '_Dbg_debug_trap_handler 0 "$BASH_COMMAND" "$@"' DEBUG
/usr/local/share/bashdb/bashdb-trace:  trap '_Dbg_debug_trap_handler 0 "$BASH_COMMAND" "$@"' DEBUG
/usr/local/share/bashdb/dbg-fns.inc:  trap '_Dbg_debug_trap_handler 0 "$BASH_COMMAND" "$@"' DEBUG
/usr/local/share/bashdb/dbg-main.inc:trap '_Dbg_debug_trap_handler 0 "$BASH_COMMAND" "$@"' DEBUG
/usr/local/share/bashdb/dbg-sig.inc:    trap '_Dbg_exit_handler "$BASH_COMMAND"' EXIT
/usr/local/share/bashdb/dbg-sig.inc:    local trap_cmd="trap '_Dbg_sig_handler $signum \"\$BASH_COMMAND\" \"\$@\"' $name"

その他に、プロンプトを書き換えたり、functraceを設定したり、とても普通のbashでは使うことがないことが盛り沢山です。

$ grep PS4 /usr/local/share/bashdb/*
/usr/local/share/bashdb/dbg-cmds.inc:  # We've reset some variables like IFS and PS4 to make eval look
/usr/local/share/bashdb/dbg-fns.inc:  PS4='+ dbg (${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]}\n'
$ grep functrace /usr/local/share/bashdb/*
/usr/local/share/bashdb/bashdb-trace:  set -o functrace

*1:env LANG=C man bash