Imported from ../bash-2.05b.tar.gz.
[platform/upstream/bash.git] / examples / bashdb / bashdb
old mode 100644 (file)
new mode 100755 (executable)
index 97d287d..2bca9f9
-# kshdb - Korn Shell Debugger main file
-# adapted from 'Learning the Korn Shell' by Bill Rosenblatt (O'Reilly)
-# by Cigy Cyriac (cigy@felix.tulblr.unisys.com)
-# Main driver: constructs full script (with preamble) and runs it
+#! /bin/bash
+# bashdb - Bash shell debugger
+#
+# Adapted from an idea in O'Reilly's `Learning the Korn Shell'
+# Copyright (C) 1993-1994 O'Reilly and Associates, Inc.
+# Copyright (C) 1998, 1999, 2001 Gary V. Vaughan <gvv@techie.com>>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
 
-echo 'Bourne-Again Shell Debugger version 0.1'
+# NOTE:
+#
+# This program requires bash 2.x.
+# If bash 2.x is installed as "bash2", you can invoke  bashdb like this:
+#
+#   DEBUG_SHELL=/bin/bash2 /bin/bash2 bashdb script.sh
 
-_pname=${0##*/}
+# TODO:
+#
+# break [regexp]
+# cond [break] [condition]
+# tbreak [regexp|+lines]
+# restart
+# Variable watchpoints
+# Instrument `source' and `.' files in $_potbelliedpig
+# be cleverer about lines we allow breakpoints to be set on
+# break [function_name]
 
-[ $# -eq 0 ] && {
-       echo "${_pname}: usage: ${_pname} <script_file>"
-       exit 1
-}
+echo 'Bash Debugger version 1.2.4'
+
+export _dbname=${0##*/}
+
+if test $# -lt 1; then
+  echo "$_dbname: Usage: $_dbname filename" >&2
+  exit 1
+fi
 
 _guineapig=$1
 
-[ -r $_guineapig ] || {
-       echo "${_pname}: cannot read $_guineapig." >&2
-       exit 1
-}
+if test ! -r $1; then
+  echo "$_dbname: Cannot read file '$_guineapig'." >&2
+  exit 1
+fi
+
 shift
 
-_tmpdir=/tmp
-_libdir=.
-_dbgfile=$_tmpdir/bashdb$$             #temp file for script being debugged
+__debug=${TMPDIR-/tmp}/bashdb.$$
+sed -e '/^# bashdb - Bash shell debugger/,/^# -- DO NOT DELETE THIS LINE -- /d' "$0" > $__debug
+cat $_guineapig >> $__debug
+exec ${DEBUG_SHELL-bash} $__debug $_guineapig "$@"
 
-cat $_libdir/bashdb.pre $_guineapig > $_dbgfile
-if [ -f "$BASH" ]; then
-       exec $BASH $_dbgfile $_guineapig $_tmpdir $_libdir "$@"
-else
-       exec bash $_dbgfile $_guineapig $_tmpdir $_libdir "$@"
-fi
-# end of bashdb
+exit 1
+
+# -- DO NOT DELETE THIS LINE -- The program depends on it
+
+#bashdb preamble
+# $1 name of the original guinea pig script
+
+__debug=$0
+_guineapig=$1
+__steptrap_calls=0
+
+shift
+
+shopt -s extglob       # turn on extglob so we can parse the debugger funcs
+
+function _steptrap
+{
+  local i=0
+
+  _curline=$1
+
+  if (( ++__steptrap_calls > 1 && $_curline == 1 )); then
+    return
+  fi
+
+  if [ -n "$_disps" ]; then
+    while (( $i < ${#_disps[@]} ))
+    do
+      if [ -n "${_disps[$i]}" ]; then
+        _msg "${_disps[$i]}: \c"
+        eval _msg ${_disps[$i]}
+      fi
+      let i=$i+1
+    done
+  fi
+
+  if (( $_trace )); then
+    _showline $_curline
+  fi
+
+  if (( $_steps >= 0 )); then
+    let _steps="$_steps - 1"
+  fi
+
+  if _at_linenumbp ; then
+    _msg "Reached breakpoint at line $_curline"
+    _showline $_curline
+    _cmdloop
+  elif [ -n "$_brcond" ] && eval $_brcond; then
+    _msg "Break condition $_brcond true at line $_curline"
+    _showline $_curline
+    _cmdloop
+  elif (( $_steps == 0 )); then
+    # Assuming a real script will have the "#! /bin/sh" at line 1,
+    # assume that when $_curline == 1 we are inside backticks.
+    if (( ! $_trace )); then
+      _msg "Stopped at line $_curline"
+      _showline $_curline
+    fi
+    _cmdloop
+  fi
+}
+
+function _setbp
+{
+  local i f line _x
+
+  if [ -z "$1" ]; then
+    _listbp
+    return
+  fi
+
+  eval "$_seteglob"
+
+  if [[ $1 == *(\+)[1-9]*([0-9]) ]]; then
+    case $1 in
+    +*)
+      # normalize argument, then double it (+2 -> +2 + 2 = 4)
+      _x=${1##*([!1-9])}       # cut off non-numeric prefix
+      _x=${x%%*([!0-9])}       # cut off non-numeric suffix
+      f=$(( $1 + $_x ))
+      ;;
+    *)
+      f=$(( $1 ))
+      ;;
+    esac
+
+    # find the next valid line
+    line="${_lines[$f]}"
+    while _invalidbreakp $f
+    do
+      (( f++ ))
+      line="${_lines[$f]}"
+    done
+
+    if (( $f != $1 ))
+    then
+      _msg "Line $1 is not a valid breakpoint"
+    fi
+
+    if [ -n "${_lines[$f]}" ]; then
+      _linebp[$1]=$1;
+      _msg "Breakpoint set at line $f"
+    else
+      _msg "Breakpoints can only be set on executable lines"
+    fi
+  else
+    _msg "Please specify a numeric line number"
+  fi
+
+  eval "$_resteglob"
+}
+
+function _listbp
+{
+  local i
+  
+  if [ -n "$_linebp" ]; then
+    _msg "Breakpoints:"
+    for i in ${_linebp[*]}; do
+      _showline $i
+    done
+  else
+    _msg "No breakpoints have been set"
+  fi
+}
+
+function _clearbp
+{
+  local i
+
+  if [ -z "$1" ]; then
+    read -e -p "Delete all breakpoints? "
+    case $REPLY in
+    [yY]*)
+      unset _linebp[*]
+      _msg "All breakpoints have been cleared"
+      ;;
+    esac
+    return 0
+  fi
+
+  eval "$_seteglob"
+
+  if [[ $1 == [1-9]*([0-9]) ]]; then
+    unset _linebp[$1]
+    _msg "Breakpoint cleared at line $1"
+  else
+    _msg "Please specify a numeric line number"
+  fi
+
+  eval "$_resteglob"
+}
+
+function _setbc
+{
+  if (( $# > 0 )); then
+    _brcond=$@
+    _msg "Break when true: $_brcond"
+  else
+    _brcond=
+    _msg "Break condition cleared"
+  fi
+}
+
+function _setdisp
+{
+  if [ -z "$1" ]; then
+    _listdisp
+  else
+    _disps[${#_disps[@]}]="$1"
+    if (( ${#_disps[@]} < 10 ))
+    then
+      _msg " ${#_disps[@]}: $1"
+    else
+      _msg "${#_disps[@]}: $1"
+    fi
+  fi
+}
+
+function _listdisp
+{
+  local i=0 j
+  
+  if [ -n "$_disps" ]; then
+    while (( $i < ${#_disps[@]} ))
+    do
+      let j=$i+1
+    if (( ${#_disps[@]} < 10 ))
+    then
+      _msg " $j: ${_disps[$i]}"
+    else
+      _msg "$j: ${_disps[$i]}"
+    fi
+      let i=$j
+    done
+  else
+    _msg "No displays have been set"
+  fi
+}
+
+function _cleardisp
+{
+  if (( $# < 1 )) ; then
+    read -e -p "Delete all display expressions? "
+    case $REPLY in
+    [Yy]*)
+      unset _disps[*]
+      _msg "All breakpoints have been cleared"
+      ;;
+    esac
+    return 0
+  fi
+
+  eval "$_seteglob"
+
+  if [[ $1 == [1-9]*([0-9]) ]]; then
+    unset _disps[$1]
+    _msg "Display $i has been cleared"
+  else
+    _listdisp
+    _msg "Please specify a numeric display number"
+  fi
+
+  eval "$_resteglob"
+}   
+
+# usage _ftrace -u funcname [funcname...]
+function _ftrace
+{
+  local _opt=-t _tmsg="enabled" _func 
+  if [[ $1 == -u ]]; then
+       _opt=+t
+       _tmsg="disabled"
+       shift
+  fi
+  for _func; do
+         declare -f $_opt $_func
+         _msg "Tracing $_tmsg for function $_func"
+  done
+}
+
+function _cmdloop
+{
+  local cmd args
+
+  while read -e -p "bashdb> " cmd args; do
+    test -n "$cmd" && history -s "$cmd $args"  # save on history list
+    test -n "$cmd" || { set $_lastcmd; cmd=$1; shift; args=$*; }
+    if [ -n "$cmd" ]
+    then
+      case $cmd in
+       b|br|bre|brea|break)
+         _setbp $args
+         _lastcmd="break $args"
+         ;;
+       co|con)
+         _msg "ambiguous command: '$cmd', condition, continue?"
+         ;;
+       cond|condi|condit|conditi|conditio|condition)
+         _setbc $args
+         _lastcmd="condition $args"
+         ;;
+       c|cont|conti|contin|continu|continue)
+         _lastcmd="continue"
+         return
+         ;;
+       d)
+         _msg "ambiguous command: '$cmd', delete, display?"
+         ;;
+       de|del|dele|delet|delete)
+         _clearbp $args
+         _lastcmd="delete $args"
+         ;;
+       di|dis|disp|displ|displa|display)
+         _setdisp $args
+         _lastcmd="display $args"
+         ;;
+       f|ft|ftr|ftra|ftrace)
+         _ftrace $args
+         _lastcmd="ftrace $args"
+         ;;
+       \?|h|he|hel|help)
+         _menu
+         _lastcmd="help"
+         ;;
+       l|li|lis|list)
+         _displayscript $args
+         # _lastcmd is set in the _displayscript function
+         ;;
+       p|pr|pri|prin|print)
+         _examine $args
+         _lastcmd="print $args"
+         ;;
+       q|qu|qui|quit)
+         exit
+         ;;
+       s|st|ste|step|n|ne|nex|next)
+         let _steps=${args:-1}
+         _lastcmd="next $args"
+         return
+         ;;
+       t|tr|tra|trac|trace)
+         _xtrace
+         ;;
+       u|un|und|undi|undis|undisp|undispl|undispla|undisplay)
+         _cleardisp $args
+         _lastcmd="undisplay $args"
+         ;;
+       !*)
+         eval ${cmd#!} $args
+         _lastcmd="$cmd $args"
+         ;;
+       *)
+         _msg "Invalid command: '$cmd'"
+         ;;
+      esac
+    fi
+  done
+}
+
+function _at_linenumbp
+{
+  [[ -n ${_linebp[$_curline]} ]]
+}
+
+function _invalidbreakp
+{
+  local line=${_lines[$1]}
+
+  # XXX - should use shell patterns
+  if test -z "$line" \
+      || expr "$line" : '[ \t]*#.*' > /dev/null \
+      || expr "$line" : '[ \t]*;;[ \t]*$' > /dev/null \
+      || expr "$line" : '[ \t]*[^)]*)[ \t]*$' > /dev/null \
+      || expr "$line" : '[ \t]*;;[ \t]*#.**$' > /dev/null \
+      || expr "$line" : '[ \t]*[^)]*)[ \t]*;;[ \t]*$' > /dev/null \
+      || expr "$line" : '[ \t]*[^)]*)[ \t]*;;*[ \t]*#.*$' > /dev/null
+  then
+    return 0
+  fi
+
+  return 1
+}
+
+function _examine
+{
+  if [ -n "$*" ]; then
+    _msg "$args: \c"
+    eval _msg $args
+  else
+    _msg "Nothing to print"
+  fi
+}
+
+function _displayscript
+{
+  local i j start end bp cl
+
+  if (( $# == 1 )); then       # list 5 lines on either side of $1
+    if [ $1 = "%" ]; then
+      let start=1
+      let end=${#_lines[@]}
+    else
+      let start=$1-5
+      let end=$1+5
+    fi
+  elif (( $# > 1 )); then      # list between start and end
+    if [ $1 = "^" ]; then
+      let start=1
+    else
+      let start=$1
+    fi
+
+    if [ $2 = "\$" ]; then
+      let end=${#_lines[@]}
+    else
+      let end=$2
+    fi
+  else                         # list 5 lines on either side of current line
+    let start=$_curline-5
+    let end=$_curline+5
+  fi
+
+  # normalize start and end
+  if (( $start < 1 )); then
+    start=1
+  fi
+  if (( $end > ${#_lines[@]} )); then
+    end=${#_lines[@]}
+  fi
+
+  cl=$(( $end - $start ))
+  if (( $cl > ${LINES-24} )); then
+    pager=${PAGER-more}
+  else
+    pager=cat
+  fi
+  
+  i=$start
+  ( while (( $i <= $end )); do
+      _showline $i
+      let i=$i+1
+    done ) 2>&1 | $pager
+
+  # calculate the next block of lines
+  start=$(( $end + 1 ))
+  end=$(( $start + 11 ))
+  if (( $end > ${#_lines[@]} ))
+  then
+    end=${#_lines[@]}
+  fi
+
+  _lastcmd="list $start $end"
+}
+
+function _xtrace
+{
+  let _trace="! $_trace"
+  if (( $_trace )); then
+    _msg "Execution trace on"
+  else
+    _msg "Execution trace off"
+  fi
+}
+       
+function _msg
+{
+  echo -e "$@" >&2
+}
+
+function _showline
+{
+  local i=0 bp=' ' line=$1 cl=' '
+
+  if [[ -n ${_linebp[$line]} ]]; then
+    bp='*'
+  fi
+
+  if  (( $_curline == $line )); then
+    cl=">"
+  fi
+
+  if (( $line < 100 )); then
+    _msg "$_guineapig:$line   $bp $cl${_lines[$line]}"
+  elif (( $line < 10 )); then
+    _msg "$_guineapig:$line  $bp $cl${_lines[$line]}"
+  elif (( $line > 0 )); then
+    _msg "$_guineapig:$line $bp $cl${_lines[$line]}"
+  fi
+}
+
+function _cleanup
+{
+  rm -f $__debug $_potbelliedpig 2> /dev/null
+}
+
+function _menu
+{
+  _msg 'bashdb commands:
+       break N         set breakpoint at line N
+       break           list breakpoints & break condition
+       condition foo   set break condition to foo
+       condition       clear break condition
+       delete N        clear breakpoint at line N
+       delete          clear all breakpoints
+       display EXP     evaluate and display EXP for each debug step
+       display         show a list of display expressions
+       undisplay N     remove display expression N
+       list N M        display all lines of script between N and M
+       list N          display 5 lines of script either side of line N
+       list            display 5 lines if script either side of current line
+       continue        continue execution upto next breakpoint
+       next [N]        execute [N] statements (default 1)
+       print expr      prints the value of an expression
+       trace           toggle execution trace on/off
+       ftrace [-u] func        make the debugger step into function FUNC
+                       (-u turns off tracing FUNC)
+       help            print this menu
+       ! string        passes string to a shell
+       quit            quit'
+}
+
+shopt -u extglob
+
+HISTFILE=~/.bashdb_history
+set -o history
+set +H
+
+# strings to save and restore the setting of `extglob' in debugger functions
+# that need it
+_seteglob='local __eopt=-u ; shopt -q extglob && __eopt=-s ; shopt -s extglob'
+_resteglob='shopt $__eopt extglob'
+
+_linebp=()
+let _trace=0
+let _i=1
+
+# Be careful about quoted newlines
+_potbelliedpig=${TMPDIR-/tmp}/$_guineapig.$$
+sed 's,\\$,\\\\,' $_guineapig > $_potbelliedpig
+
+_msg "Reading source from file: $_guineapig"
+while read; do
+  _lines[$_i]=$REPLY
+  let _i=$_i+1
+done < $_potbelliedpig
+
+trap _cleanup EXIT
+# Assuming a real script will have the "#! /bin/sh" at line 1,
+# don't stop at line 1 on the first run
+let _steps=1
+LINENO=-1
+trap '_steptrap $LINENO' DEBUG