# -*- shell-script -*-
# fns.sh - Debugger Utility Functions
#
# Copyright (C) 2002-2011 2019 2021
# Rocky Bernstein <rocky@gnu.org>
#
# 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, 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; see the file COPYING. If not, write to
# the Free Software Foundation, 59 Temple Place, Suite 330, Boston,
# MA 02111 USA.
typeset -a _Dbg_yn; _Dbg_yn=("n" "y")
# Return $2 copies of $1. If successful, $? is 0 and the return value
# is in _Dbg_result. Otherwise $? is 1 and _Dbg_result ''
function _Dbg_copies {
_Dbg_result=''
(( $# < 2 )) && return 1
typeset -r string="$1"
typeset -i count=$2 || return 2
(( count > 0 )) || return 3
builtin printf -v _Dbg_result "%${count}s" ' ' || return 3
_Dbg_result=${_Dbg_result// /$string}
return 0
}
# _Dbg_defined returns 0 if $1 is a defined variable or nonzero otherwise.
_Dbg_defined() {
(( 0 == $# )) && return 1
if (( (BASH_VERSINFO[0] > 4) ||
(BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] >= 2) )); then
# Without the eval below, bash > 4.2 will barf. The quotes
# hide invalid lingo for earlier bash..
eval "[[ -v \"$1\" ]]"
else
typeset -p "$1" &> /dev/null
fi
}
# Add escapes to a string $1 so that when it is read back via "$1"
# it is the same as $1.
function _Dbg_esc_dq {
builtin printf "%q\n" "$1"
}
# We go through the array indirection below because bash (but not ksh)
# gives syntax errors when this is put in place.
typeset -a _Dbg_eval_re;
_Dbg_eval_re=(
'^[[:blank:]]*(if|elif)[[:blank:]]+([^;]*)((;[[:blank:]]*then?)?|$)'
'^[[:blank:]]*return[[:blank:]]+(.*)$'
'^[[:blank:]]*while[[:blank:]]+([^;]*)((;[[:blank:]]*do?)?|$)'
'^[[:blank:]]*[A-Za-z_][A-Za-z_0-9_]*[+-]?=(.*$)'
"^[[:blank:]]*[A-Za-z_][A-Za-z_0-9_]*\[[0-9]+\][+-]?=(.*\$)"
)
# Removes "[el]if" .. "; then" or "while" .. "; do" or "return .."
# leaving resumably the expression part. This function is called by
# the eval? command where we want to evaluate the expression part in
# a source line of code
_Dbg_eval_extract_condition()
{
orig="$1"
if [[ $orig =~ ${_Dbg_eval_re[0]} ]] ; then
extracted=${BASH_REMATCH[2]}
elif [[ $orig =~ ${_Dbg_eval_re[1]} ]] ; then
extracted="echo ${BASH_REMATCH[1]}"
elif [[ $orig =~ ${_Dbg_eval_re[2]} ]] ; then
extracted=${BASH_REMATCH[1]}
elif [[ $orig =~ ${_Dbg_eval_re[3]} ]] ; then
extracted="echo ${BASH_REMATCH[1]}"
elif [[ $orig =~ ${_Dbg_eval_re[4]} ]] ; then
extracted="echo ${BASH_REMATCH[1]}"
else
extracted=$orig
fi
}
# Print "on" or "off" depending on whether $1 is true (0) or false
# (nonzero).
function _Dbg_onoff {
typeset onoff='off.'
(( $1 != 0 )) && onoff='on.'
builtin echo $onoff
}
# Set $? to $1 if supplied or the saved entry value of $?.
function _Dbg_set_dol_q {
return ${1:-$_Dbg_debugged_exit_code}
}
# Split $2 using $1 as the split character. We accomplish this by
# temporarily resetting the variable IFS (input field separator).
#
# Example:
# typeset -a a=($(_Dbg_split ':' "file:line"))
# a[0] will have file and a{1] will have line.
function _Dbg_split {
typeset old_IFS=$IFS
typeset new_ifs=${1:-' '}
shift
typeset -r text="$*"
typeset -a array
IFS="$new_ifs"
array=( $text )
echo ${array[@]}
IFS=$old_IFS
}
# _get_function echoes a list of all of the functions.
# if $1 is nonzero, system functions, i.e. those whose name starts with
# an underscore (_), are included in the search.
# FIXME add parameter search pattern.
_Dbg_get_functions() {
typeset -i include_system=${1:-0}
typeset pat=${2:-.*}
typeset -a fns_a
fns_a=( $(declare -F) )
typeset -a ret_fns=()
typeset -i i
typeset -i invert=0;
if [[ $pat == !* ]] ; then
# Remove leading !
pat=#{$pat#!}
invert=1
fi
# Iterate skipping over consecutive single tokens "declare" and "-F"
for (( i=2; (( i < ${#fns_a[@]} )) ; i += 3 )) ; do
typeset fn="${fns_a[$i]}"
[[ $fn == _* ]] && (( ! include_system )) && continue
if [[ $fn =~ $pat ]] ; then
[[ $invert == 0 ]] && ret_fns[${#ret_fns[@]}]=$fn
else
[[ $invert != 0 ]] && ret_fns[${#ret_fns[@]}]=$fn
fi
done
echo ${ret_fns[@]}
}
# _Dbg_is_function returns 0 if $1 is a defined function or nonzero otherwise.
# if $2 is nonzero, system functions, i.e. those whose name starts with
# an underscore (_), are included in the search.
_Dbg_is_function() {
(( 0 == $# )) && return 1
typeset needed_fn=$1
typeset -i include_system=${2:-0}
[[ ${needed_fn:0:1} == '_' ]] && ((!include_system)) && {
return 1
}
declare -F $needed_fn >/dev/null 2>&1
return $?
}
# Return 0 if set -x tracing is on
_Dbg_is_traced() {
# Is "x" in set options?
if [[ $- == *x* ]] ; then
return 0
else
return 1
fi
}
# Common routine for setup of commands that take a single
# linespec argument. We assume the following variables
# which we store into:
# filename, line_number, full_filename
function _Dbg_linespec_setup {
typeset linespec=${1:-''}
if [[ -z $linespec ]] ; then
_Dbg_errmsg "Invalid line specification, null given"
fi
typeset -a word
eval "word=($(_Dbg_parse_linespec $linespec))"
if [[ ${#word[@]} == 0 ]] ; then
_Dbg_errmsg "Invalid line specification: $linespec"
return
fi
filename="${word[2]}"
typeset -ri is_function=${word[1]}
line_number=${word[0]}
full_filename=$(_Dbg_is_file "$filename")
if (( is_function )) ; then
if [[ -z "$full_filename" ]] ; then
_Dbg_readin "$filename"
full_filename=$(_Dbg_is_file "$filename")
fi
fi
}
# Parse linespec in $1 which should be one of
# int
# file:line
# function-num
# Return triple (line, is-function?, filename,)
# We return the filename last since that can have embedded blanks.
function _Dbg_parse_linespec {
typeset linespec=$1
eval "$_seteglob"
case "$linespec" in
# line number only - use _Dbg_frame_last_filename for filename
$int_pat )
echo "$linespec 0 \"$_Dbg_frame_last_filename\""
;;
# file:line
[^:][^:]*[:]$int_pat )
# Split the POSIX way
typeset line_word=${linespec##*:}
typeset file_word=${linespec%${line_word}}
file_word=${file_word%?}
echo "$line_word 0 \"$file_word\""
;;
# Function name or error
* )
if _Dbg_is_function $linespec $_Dbg_set_debug ; then
local -a word=( $(declare -F $linespec) )
if [[ 0 == $? && ${#word[@]} > 2 ]]; then
builtin echo "${word[1]} 1 ${word[2]}"
else
builtin echo ''
fi
else
builtin echo ''
fi
;;
esac
}
# usage _Dbg_set_ftrace [-u] funcname [funcname...]
# Sets or unsets a function for stopping by setting
# the -t or +t property to the function declaration.
#
function _Dbg_set_ftrace {
typeset opt=-t tmsg="enabled" func
if [[ $1 == -u ]]; then
opt=+t
tmsg="disabled"
shift
fi
for func; do
declare -f $opt $func
# _Dbg_msg "Tracing $tmsg for function $func"
done
}
# Adapted from
# https://stackoverflow.com/questions/14525296/bash-check-if-variable-is-array
function _Dbg_is_readonly_array() {
# no argument passed
if [[ $# -ne 1 ]]; then
return 1
fi
typeset var=$1
# use a variable to avoid having to escape spaces
regex="^declare -ar ${var}(=|$)"
if [[ $(declare -p "$var" 2> /dev/null) =~ $regex ]]; then
return 0
else
return 1
fi
}