Skip to content

Commit

Permalink
Rewrite "info variables" to support line feeds and spaces in values, …
Browse files Browse the repository at this point in the history
…array keys and array values.

Bash's "typeset -p" escapes line feeds and other special characters and wraps such values in $''. Array keys with spaces are wrapped into "".
Previously, $'' was kept but the escape codes were resolved. The escape codes are now retained in the output of "info variables" to show each variable and its value on a single line. This is to improve readability and help programmatic parsing of the variable listing.
  • Loading branch information
jansorg committed Jul 4, 2024
1 parent dda77ff commit 36b1888
Show file tree
Hide file tree
Showing 3 changed files with 255 additions and 151 deletions.
252 changes: 117 additions & 135 deletions command/info_sub/variables.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
# with bashdb; see the file COPYING. If not, write to the Free Software
# Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA.


# V [![pat]] List variables and values for whose variables names which
# match pat $1. If ! is used, list variables that *don't* match.
# If pat ($1) is omitted, use * (everything) for the pattern.
Expand Down Expand Up @@ -56,147 +55,130 @@ See also:
' 1

if ! typeset -F getopts_long >/dev/null 2>&1 ; then
if ! typeset -F getopts_long >/dev/null 2>&1; then
# shellcheck source=./../../getopts_long.sh
. "${_Dbg_libdir}/getopts_long.sh"
fi

# Should declare typset_flags before calling.
# That it the implit returned value
function _Dbg_info_variables_parse_options {

typeset -i _Dbg_rc=0
function _Dbg_do_info_variables() {
declare _Dbg_typeset_flags=""
# 0: print variables with flags
# 1: filter by flags in $_Dbg_typeset_flags and don't print flags
declare -i _Dbg_typeset_filtered=1
_Dbg_info_variables_parse_options "$@"
(($? != 0)) && return

# Caveats:
# Bash >= 5.2: 'declare -p' properly escapes special characters within $'', e.g. $'\n\t'
# Bash < 5.2: 'declare -p' does not escape special characters within $'', but only 'declare' does
# "declare -p" outputs variables without values, but "declare" does not
#
# To work with all of Bash 5.x, we're collecting all variables and values from 'declare'.
# Because a plain "declare" also prints functions we're only iterating until the first function definition was found.
#
# Then we run declare with the filter parameters (-p, -i, etc.) to retrieve the variables to output.

# create an associative array of all available variables with bash-escaped values
declare _Dbg_var_line
declare -A _Dbg_var_values=()
while IFS=$'\n' read -r _Dbg_var_line; do
# skip _Dbg variables
if [[ $_Dbg_var_line =~ ^_Dbg_ ]]; then continue; fi

# break when first function was found
if [[ $_Dbg_var_line =~ ^[^=\ ]+\ \(\)$ || $_Dbg_var_line =~ ^\{ ]]; then
break
fi

# record escaped key-value pair, allow for empty values as in "-- name="
if [[ $_Dbg_var_line =~ ^([^=]+)=(.*)$ ]]; then
_Dbg_var_values["${BASH_REMATCH[1]}"]="${BASH_REMATCH[2]}"
fi
done < <(declare)

declare _Dbg_var_flags _Dbg_var_name
if [[ $_Dbg_typeset_filtered -eq 0 ]]; then
# run "declare -p" and print flags, name and value
while IFS=$'\n' read -r _Dbg_var_line; do
if [[ $_Dbg_var_line =~ ^(declare|local)\ (-[^\ ]+)\ ([^=]+)= ]]; then
_Dbg_var_name="${BASH_REMATCH[3]}"
if [[ -n ${_Dbg_var_values["$_Dbg_var_name"]+x} ]]; then
_Dbg_msg_verbatim "${BASH_REMATCH[2]} $_Dbg_var_name=${_Dbg_var_values["$_Dbg_var_name"]}"
fi
elif [[ $_Dbg_var_line =~ ^(declare|local)\ (-[^\ ]+)\ ([^=]+) ]]; then
# no value, only print flags and name
_Dbg_var_name="${BASH_REMATCH[3]}"
if [[ -n ${_Dbg_var_values["$_Dbg_var_name"]+x} ]]; then
_Dbg_msg_verbatim "${BASH_REMATCH[2]} $_Dbg_var_name"
fi
fi
done < <(declare -p)
elif [[ -n "$_Dbg_typeset_flags" ]]; then
# run "declare $_Dbg_typeset_flags" and print name and value
while IFS=$'\n' read -r _Dbg_var_line; do
if [[ $_Dbg_var_line =~ ^(declare|local)\ (-[^\ ]+)\ ([^=]+)= ]]; then
_Dbg_var_name="${BASH_REMATCH[3]}"
if [[ -n ${_Dbg_var_values["$_Dbg_var_name"]+x} ]]; then
_Dbg_msg_verbatim "$_Dbg_var_name=${_Dbg_var_values["$_Dbg_var_name"]}"
fi
fi
done < <(declare $_Dbg_typeset_flags)
else
# for a plain "declare" just print available name=value pairs to avoid calling "declare",
# which would output function declarations
for _Dbg_var_name in "${!_Dbg_var_values[@]}"; do
if [[ -n ${_Dbg_var_values["$_Dbg_var_name"]+x} ]]; then
_Dbg_msg_verbatim "$_Dbg_var_name=${_Dbg_var_values["$_Dbg_var_name"]}"
fi
done
fi
}

# Parse flags passed to the "info variables" command.
# The caller should declare _Dbg_typeset_flags and _Dbg_typeset_filtered before calling,
# which are implicitly returned values.
function _Dbg_info_variables_parse_options {
_Dbg_typeset_flags=""
_Dbg_typeset_filtered=1

OPTLIND=''
while getopts_long irxaAtp opt \
integer no_argument \
readonly no_argument \
exports no_argument \
indexed no_argument \
associative no_argument \
trace no_argument \
properties no_argument \
'' $@
do
case "$opt" in
i | integer )
_Dbg_typeset_flags="-i $_Dbg_typeset_flags";;
r | readonly )
_Dbg_typeset_flags="-r _$_Dbg_typeset_flags";;
x | exports )
_Dbg_typeset_flags="-x $_Dbg_typeset_flags";;
a | indexed )
_Dbg_typeset_flags="-a $_Dbg_typeset_flags";;
A | associative )
_Dbg_typeset_flags="-A $_Dbg_typeset_flags";;
t | trace )
_Dbg_typeset_flags="-t $_Dbg_typeset_flags";;
p | properties )
_Dbg_typeset_filtered=0;;
* )
_Dbg_errmsg "Invalid argument in $@; use only -x, -i, -r, -a, -A, -t, or -p"
_Dbg_rc=1
;;
esac
typeset -i _Dbg_rc=0
typeset OPTLIND=''
while getopts_long irxaAtp opt \
integer no_argument \
readonly no_argument \
exports no_argument \
indexed no_argument \
associative no_argument \
trace no_argument \
properties no_argument \
'' "$@"; do
case "$opt" in
i | integer)
_Dbg_typeset_flags="-i $_Dbg_typeset_flags"
;;
r | readonly)
_Dbg_typeset_flags="-r $_Dbg_typeset_flags"
;;
x | exports)
_Dbg_typeset_flags="-x $_Dbg_typeset_flags"
;;
a | indexed)
_Dbg_typeset_flags="-a $_Dbg_typeset_flags"
;;
A | associative)
_Dbg_typeset_flags="-A $_Dbg_typeset_flags"
;;
t | trace)
_Dbg_typeset_flags="-t $_Dbg_typeset_flags"
;;
p | properties)
_Dbg_typeset_filtered=0
;;
*)
_Dbg_errmsg "Invalid argument in $*; use only -x, -i, -r, -a, -A, -t, or -p"
_Dbg_rc=1
;;
esac
done
return $_Dbg_rc
}

function _Dbg_do_info_variables {
_Dbg_typeset_flags=""
local -i _Dbg_typeset_filtered=1
_Dbg_info_variables_parse_options "$@"
(( $? != 0 )) && return

local _Dbg_old_glob="$GLOBIGNORE"
GLOBIGNORE="*"

_Dbg_match='*'
local _Dbg_list=$(declare -p $_Dbg_typeset_flags)
local _Dbg_old_ifs=${IFS}
IFS="
"
local _Dbg_temp=${_Dbg_list}
_Dbg_list=""
local -i _Dbg_i=0
local -a _Dbg_list

# GLOBIGNORE protects us against using the result of
# a glob expansion, but it doesn't protect us from
# actually performing it, and this can bring bash down
# with a huge _Dbg_source_ variable being globbed.
# So here we disable globbing momentarily
set -o noglob
for _Dbg_item in ${_Dbg_temp}; do
_Dbg_list[${_Dbg_i}]="${_Dbg_item}"
_Dbg_i=${_Dbg_i}+1
done
set +o noglob
IFS=${_Dbg_old_ifs}
local _Dbg_item=""
local _Dbg_skip=0
local _Dbg_show_cmd=""
_Dbg_show_cmd=`echo -e "case \\${_Dbg_item} in \n${_Dbg_match})\n echo yes;;\n*)\necho no;; esac"`

for (( _Dbg_i=0; (( _Dbg_i < ${#_Dbg_list[@]} )) ; _Dbg_i++ )) ; do
_Dbg_item=${_Dbg_list[$_Dbg_i]}


# Ignore all _Dbg_ variables here because the following
# substitutions takes a long while when it encounters
# a big _Dbg_source_
if [[ ${_Dbg_item} =~ "_Dbg_" ]] ; then
continue;
fi


case ${_Dbg_item} in
*\ \(\)\ )
_Dbg_skip=1
;;
\})
_Dbg_skip=0
continue
esac
if [[ _Dbg_skip -eq 1 ]]; then
continue
fi
_Dbg_item=${_Dbg_item/=/==/}
_Dbg_item=${_Dbg_item%%=[^=]*}
case ${_Dbg_item} in
_=);;
*=)
_Dbg_item=${_Dbg_item%=}
local _Dbg_show=`eval $_Dbg_show_cmd`
if [[ "$_Dbg_show" != "$_Dbg_match_inverted" ]]; then
if [[ -n ${_Dbg_item} ]]; then
local _Dbg_var=`declare -p ${_Dbg_typeset_flags} ${_Dbg_item} 2>/dev/null`
if [[ -n "$_Dbg_var" ]]; then
# Uncomment the following 3 lines to use literal
# linefeeds
# _Dbg_var=${_Dbg_var//\\\\n/\\n}
# _Dbg_var=${_Dbg_var//
#/\n}
# Comment the following 3 lines to use literal linefeeds
_Dbg_var=${_Dbg_var//\\\\n/\\\\\\n}
_Dbg_var=${_Dbg_var//
/\\n}
if (( _Dbg_typeset_filtered == 1 )); then
_Dbg_var=${_Dbg_var#* * }
else
_Dbg_var=${_Dbg_var#* }
fi
_Dbg_msg ${_Dbg_var}
fi
fi
fi
;;
*)
;;
esac

done
GLOBIGNORE=$_Dbg_old_glob
}
15 changes: 15 additions & 0 deletions lib/msg.sh
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,21 @@ function _Dbg_printf_nocr {
fi
}

# Like _Dbg_msg but does not evaluate escape sequences which are embedded in the arguments
# print message to output device
function _Dbg_msg_verbatim {
if (( _Dbg_logging )) ; then
builtin echo -E "$@" >>$_Dbg_logging_file
fi
if (( ! _Dbg_logging_redirect )) ; then
if [[ -n $_Dbg_tty ]] && [[ $_Dbg_tty != '&1' ]] ; then
builtin echo -E "$@" >>$_Dbg_tty
else
builtin echo -E "$@"
fi
fi
}

typeset _Dbg_dashes='---------------------------------------------------'

# print message to output device
Expand Down
Loading

0 comments on commit 36b1888

Please sign in to comment.