📄 bashdb
字号:
#! /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.# 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# 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]echo 'Bash Debugger version 1.2.4'export _dbname=${0##*/}if test $# -lt 1; then echo "$_dbname: Usage: $_dbname filename" >&2 exit 1fi_guineapig=$1if test ! -r $1; then echo "$_dbname: Cannot read file '$_guineapig'." >&2 exit 1fishift__debug=${TMPDIR-/tmp}/bashdb.$$sed -e '/^# bashdb - Bash shell debugger/,/^# -- DO NOT DELETE THIS LINE -- /d' "$0" > $__debugcat $_guineapig >> $__debugexec ${DEBUG_SHELL-bash} $__debug $_guineapig "$@"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=0shiftshopt -s extglob # turn on extglob so we can parse the debugger funcsfunction _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 extglobHISTFILE=~/.bashdb_historyset -o historyset +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=0let _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+1done < $_potbelliedpigtrap _cleanup EXIT# Assuming a real script will have the "#! /bin/sh" at line 1,# don't stop at line 1 on the first runlet _steps=1LINENO=-1trap '_steptrap $LINENO' DEBUG
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -