linuxBean14.04(28)シェルスクリプトbashdb

2015-06-05

旧ブログ

t f B! P L

前の関連記事:linuxBean14.04(27)Geanyとシェルスクリプトのデバッグ


linuxBean14.04(27)Geanyとシェルスクリプトのデバッグで使ったbashdbはシェルスクリプトで書いてあります。使い方がいまいちよくわからないので、ソースをわかる範囲で読んでみました。読んでわかったことはlinuxBean14.04(27)Geanyとシェルスクリプトのデバッグに反映済です。
(2016.5.25追記。もっとGUIで操作できるBashEclipseを使うことにしました。linuxBean14.04(116)BashEclipseでシェルスクリプトのデバッグ参照。)

bashdbはbashのシェルスクリプトで/usr/share/bashdbにスクリプトがある


bashをインストール後whereisでbashのソースファイルの場所を調べます。

pq@pq-VirtualBox:~$ whereis bashdb
bashdb: /usr/bin/bashdb /usr/bin/X11/bashdb /usr/share/bashdb /usr/share/man/man1/bashdb.1.gz

/usr/bin/bashdbをGeanyで開いてみると一行目に#!/bin/bashと書いてあるのでbashdbはbashスクリプトであることがわかります。
. ${_Dbg_libdir}/bashdb-part2.sh
/usr/bin/bashdbからbashdb-part2.shを呼び出しています。

_Dbg_libdirは53行目で/usr/share/bashdbが代入されているので/usr/share/bashdb/bashdb-part2.shを呼び出していることになります。

bash --debug /usr/bin/bashdb --highlight /usr/bin/bashdb

これでbashdbをbashdebでデバッグしてステップインしていくと/usr/share/bashdb/lib/journal.shに入ってから「引数が多すぎます」と言われるようになってその72行目で終了してしまいました。

とりあえずそれまでに呼ばれているファイルはすべて/usr/share/bashdbにあるものであることがわかりました。

対話式に実行している部分を見たかったのですがどこにあるのか探し出せませんでした。

bashdbプロンプトで実行するコマンドは/usr/share/bashdbにあるスクリプト内で関数として定義されていることがわかりました。

コマンドのエイリアス一覧を得る


コマンドのエイリアスは_Dbg_alias_add 'エイリアス' 'コマンド' で定義されているとわかったので、/usr/share/bashdbにあるファイルを_Dbg_alias_addで検索します。

pq@pq-VirtualBox:/usr/share/bashdb$ grep -r  "_Dbg_alias_add"
command/shell.sh:_Dbg_alias_add sh shell
command/finish.sh:_Dbg_alias_add fin finish
command/examine.sh:_Dbg_alias_add 'x' 'examine'
command/next.sh:_Dbg_alias_add 'n'  'next'
command/help.sh:_Dbg_alias_add 'h' help
command/help.sh:_Dbg_alias_add '?' help
command/clear.sh:_Dbg_alias_add d clear
command/alias.sh:  _Dbg_alias_add $1 $2
command/eval.sh:_Dbg_alias_add 'ev' 'eval'
command/eval.sh:_Dbg_alias_add 'ev?' 'eval'
command/eval.sh:_Dbg_alias_add 'eval?' 'eval'
command/eval.sh:_Dbg_alias_add 'pr' 'print'
command/watch.sh:_Dbg_alias_add W watch
command/watch.sh:_Dbg_alias_add We
command/action.sh:_Dbg_alias_add 'a' 'action'
command/break.sh:_Dbg_alias_add b break
command/history.sh:_Dbg_alias_add '!' 'history'
command/edit.sh:_Dbg_alias_add 'ed' 'edit'
command/up.sh:_Dbg_alias_add 'u' up
command/step.sh:_Dbg_alias_add 's'  'step'
command/step.sh:_Dbg_alias_add 's+' 'step+'
command/step.sh:_Dbg_alias_add 's-' 'step-'
command/info.sh:_Dbg_alias_add i info
command/backtrace.sh:_Dbg_alias_add bt backtrace
command/backtrace.sh:_Dbg_alias_add T backtrace
command/backtrace.sh:_Dbg_alias_add where backtrace
command/run.sh:_Dbg_alias_add R run
command/run.sh:_Dbg_alias_add restart run
command/list.sh:_Dbg_alias_add l list
command/list.sh:_Dbg_alias_add "l>" list
command/list.sh:_Dbg_alias_add "list>" list
command/continue.sh:_Dbg_alias_add 'c' 'continue'
command/continue.sh:_Dbg_alias_add 'cont' 'continue'
command/quit.sh:_Dbg_alias_add q quit
command/quit.sh:_Dbg_alias_add q! quit
command/quit.sh:_Dbg_alias_add exit quit
lib/alias.sh:_Dbg_alias_add() {

最後のlib/alias.sh:_Dbg_alias_add()はエイリアスを定義する関数部分ですね。

エイリアスを追加する


displayにエイリアスがなかったので自分で追加することにしました。

/usr/share/bashdb/lib/display.shをファイルマネージャで右クリックしてアプリケーションで開く→Leafpad(root)で開きます。
_Dbg_alias_add Di display
_Dbg_alias_add uD undisplay
最後に上記を追加しました。

displayのエイリアスをDiundisplayuDにしました。

確認するとちゃんとエイリアスは動作しましたがまだ使い込んでいないので支障がでないかは不明です。

最初はDをエイリアスにしようと思ったのですが、/usr/share/bashdb/lib/processor.shの347行目でDは使われていました。

このprocessor.shの読解は私には難しいのですが、いろいろコマンドが設定されているようです。

displayにエイリアスを設定しても、i displayを i エイリアス とはできません。

/usr/share/bashdb/command/watch.shを読んでウォッチポイントを削除する方法を調べる


watch varとwatche expr が思ったとおりに動かないのでそのソースの/usr/share/bashdb/command/watch.shをみてみることにします。
# Set or list watch command
_Dbg_do_watch_internal() {
    if [ -z "$2" ]; then
 _Dbg_clear_watch 
    else 
 typeset -i n=_Dbg_watch_max++
 _Dbg_watch_arith[$n]="$1"
 shift
 _Dbg_watch_exp[$n]="$1"
 _Dbg_watch_val[$n]=$(_Dbg_get_watch_exp_eval $n)
 _Dbg_watch_enable[$n]=1
 _Dbg_watch_count[$n]=0
 _Dbg_printf '%2d: %s==%s arith: %d' $n \
     "(${_Dbg_watch_exp[$n]})" ${_Dbg_watch_val[$n]} \
     ${_Dbg_watch_arith[$n]}
    fi
    return 0
}
この部分よりの上にある呼び出し元をみてみると、この_Dbg_do_watch_internal関数の第1引数を0にするとwatch、1にするとwatcheになるようです。

第2引数がなければ57行目で_Dbg_clear_watchを呼び出しています。

まずウォッチポイントを削除するところを探してみます。
_Dbg_do_watch() {
    typeset -a a
    a=($_Dbg_args)
    typeset first=${a[0]}
    if [[ $first == '' ]] ; then
 _Dbg_do_watch_internal 0
    elif ! _Dbg_defined "$first" ; then
 _Dbg_errmsg "Can't set watch: no such variable $first."
    else
 unset a first
 _Dbg_do_watch_internal 0 "\$$_Dbg_args"
    fi
    return $?
}
34行目で_Dbg_do_watch_internal関数が第2引数無しで呼ばれています。

変数firstが入っていなければこれが呼ばれて、変数firstは配列aのインデックス0の要素で、配列aは31行目で定義されています。

/usr/share/bashdbでgrep -r  "_Dbg_args="としてみても由来を特定できなかったので、まず_Dbg_do_watchの呼び出し元をgrepします。

するとスクリプトから呼び出し元が見つからないのでこれは対話型のコマンドから呼び出されているだけと推測できます。

ということで_Dbg_argsはgrep -r  "_Dbg_args="ででてきた、lib/processor.sh:    typeset _Dbg_args="$@"、で定義されていると思われます。

$@はコマンドの引数の配列になります。

bashdb<21> watch
Delete all watchpoints? (y/n):

watchを引数無しで実行するとウォッチポイントをすべて削除するか聞いてきました。

これですべてのウォッチポイントの削除の仕方がわかりました。

指定した番号のウォッチポイントの削除はどうするのでしょう?

grep -r  "delete"でlib/break.sh:_Dbg_delete_watch_entry() {を見つけました。

もう面倒なので詳しくは書きませんが、関数の呼び出し元と変数の代入箇所をgrepでたどっていって、結局ウォッチポイントの削除はdelete 番号wとわかりました。

/usr/share/bashdb/lib/break.shを修正してウォッチポイントの有効/無効を可能にする


deleteと同じ調子でdisable 番号wでウォッチポイントの無効化ができると考えたのですができません。

disable 5w とするとWatchpoint entry 3 doesn't exist so nothing done.という訳のわからない回答が返ってきます。

grep -r  "disable"でlib/break.sh:# Enable/disable watchpoint(s) by entry numbers.を見つけてlib/break.shの405行目をみます。
# Enable/disable watchpoint(s) by entry numbers.
_Dbg_enable_disable_watch() {
  typeset -i on=$1
  typeset en_dis=$2
  typeset -i i=$3
  if [ -n "${_Dbg_watch_exp[$i]}" ] ; then
    if [[ ${_Dbg_watch_enable[$i]} == $on ]] ; then
      _Dbg_msg "Watchpoint entry $i already $en_dis so nothing done."
    else
      _Dbg_write_journal_eval "_Dbg_watch_enable[$i]=$on"
      _Dbg_msg "Watchpoint entry $i $en_dis."
    fi
  else
    _Dbg_msg "Watchpoint entry $i doesn't exist so nothing done."
  fi
}
418行目で出力している$iがずれているのが原因なのでそれを代入している409行目がおかしいわけです。

_Dbg_enable_disable_watchの3つ目の引数を指定して呼び出しているところをgrepで探します。

同じlib/break.shの154行目にありました。
    case $i in
      $_Dbg_watch_pat )
        _Dbg_enable_disable_watch $on $en_dis ${del:0:${#del}-1}
        ;;
      $int_pat )
        _Dbg_enable_disable_brkpt $on $en_dis $i
 ;;
      * )
      _Dbg_errmsg "Invalid entry number skipped: $i"
    esac
154行目の3つ目の引数${del:0:${#del}-1}がずれているわけです。

157行目のブレークポイントの場合から類推すると$iにはウォッチポイント番号wが入っていると思うのですが、${del:0:${#del}-1}のなかに$iが見当たらないのは不思議です。

delが変数なのでパラメータの展開をみて何をしているのか考えます。

${#parameter}がparameterの値の文字数で、${parameter:offset:length}でparameter を展開したものから先頭の文字offsetから最大 length 文字を取り出します。

ということでここは間違っていないので変数delに代入している部分を探します。

pq@pq-VirtualBox:/usr/share/bashdb$ grep -r  "del="
lib/action.sh:    typeset -i del=$1
lib/break.sh:    typeset -i del=$1
lib/break.sh:    typeset -r  del="$1"
lib/break.sh:  typeset -i del=$1

うーん、どうもしっくりくるものがみつかりません。

とりあえずdelをiに書き換えて動作するかみてみます。
        _Dbg_enable_disable_watch $on $en_dis ${i:0:${#i}-1}#_Dbg_enable_disable_watch $on $en_dis ${del:0:${#del}-1}
ちゃんとenable/disable 番号wで動くようになりました。

ウォッチポイントを削除したときにメッセージを出す


デフォルトではウォッチポイントを削除しても何もメッセージが表示されませんので、表示されるようにします。

/usr/share/bashdb/lib/break.shファイルをroot権限で編集します。

ウォッチポイントを削除したときのメッセージを表示させる方法。
_Dbg_delete_watch_entry() {
  typeset -i del=$1

  if [ -n "${_Dbg_watch_exp[$del]}" ] ; then
    _Dbg_write_journal_eval "unset _Dbg_watch_exp[$del]"
    _Dbg_write_journal_eval "unset _Dbg_watch_val[$del]"
    _Dbg_write_journal_eval "unset _Dbg_watch_enable[$del]"
    _Dbg_write_journal_eval "unset _Dbg_watch_count[$del]"
    _Dbg_msg "Watchpoint entry $del has been deleted."
  else
    _Dbg_msg "Watchpoint entry $del doesn't exist so nothing done."
  fi
}
448行目を追加しました。

修正したファイル一覧


linuxBean14.04(27)Geanyとシェルスクリプトのデバッグで修正した部分もふくめた修正後のファイル一覧です。

bashdb 4.2.0.8

/usr/share/bashdb/lib/display.sh

/usr/share/bashdb/lib/break.sh

ファイル一覧linuxBean14.04(28)シェルスクリプトbashdb - p--q

参考にしたサイト


BASH with Debugger and Improved Debug Support and Error Handling
bashのシェルスクリプトのデバッガ。bashdb自身もbashのシェルスクリプトです。

Man page of BASH
bashのマニュアルの日本語訳。

第5章 シェルスクリプトプログラミング
シェルスクリプトの書き方の概略が例つきで載っています。

if 文と test コマンド - UNIX & Linux コマンド・シェルスクリプト リファレンス
bashではif文の条件部分の評価はtestコマンドで行っています。

case 文の使用方法 - UNIX & Linux コマンド・シェルスクリプト リファレンス
この解説をみるまでは「)」が変なところに出てくる理由がわかりませんでした。

shとbashでの変数内の文字列置換など - ろば電子が詰まっている
パラメータの展開のよく使いそうなものの解説。

次の関連記事:linuxBean14.04(29)LibreOfficeDev5.0SDKの例を一括でmakeする

ブログ検索 by Blogger

Translate

最近のコメント

Created by Calendar Gadget

QooQ