This commit is contained in:
2026-10-13 12:02:01 +02:00
parent daa2ee755c
commit f931db0808
11 changed files with 117 additions and 34 deletions
+79 -21
View File
@@ -37,48 +37,62 @@
load_conf "debug"
# ------------------------------------------------------------------------------
# Display a backtrace
# Display a backtrace of the current call stack (innermost frame first), one
# line per frame: function, file where it is defined, line of the call site.
# Usage: backtrace
backtrace()
{
printf "========= Call stack =========\n"
local i=1 # We begin at 1 to ignore backtrace itself
while [[ $i -lt ${#FUNCNAME[@]} ]]; do
# BASH_SOURCE[i] is the file defining FUNCNAME[i], and BASH_LINENO[i-1]
# the line of that file from which FUNCNAME[i-1] was called.
printf '%15s() %s:%d\n' \
"${FUNCNAME[$i]}" "${BASH_SOURCE[$i]}" "${BASH_LINENO[$(( i-1 ))]}"
((i++))
i=$(( i + 1 ))
done
unset i
printf "==============================\n"
}
export -f backtrace
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Function to be trapped for errors investigation
# Handler installed by settrace as ERR trap: report the failure and the call
# stack, then give back the exit status of the failed command so the trap does
# not alter the shell behaviour (e.g. under "set -e").
# Everything goes to stderr: with errtrace a failure inside $(...) or a pipe
# would otherwise inject the trace into the captured output.
# Exported because an exported settrace may be used from child bash processes,
# where the trap needs this function to exist.
error()
{
local errcode=$?
backtrace
printf "Command failed with exit status %d\n" "$errcode" >&2
backtrace >&2
return $errcode
}
export -f error
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Activate or deactivate error trapping to display backtrace
# Usage: settrace <--on|--off|--status>
# Usage: settrace <--on|--off|--status> [--force]
# Startup behaviour is set by SETTRACE_DEFAULT_ENABLED in the [debug] section.
settrace()
{
local PARSED
PARSED=$(getopt -oh --long help,on,off,status,force -- "$@")
PARSED=$(getopt -oh --long help,on,off,status,force -n settrace -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"settrace --help\" to display usage."
return 1
fi
local -a _pargs=()
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
set -- "${_pargs[@]}"
# getopt emits a shell-quoted, normalised argument list ("--" ends options)
eval set -- "$PARSED"
# First pass: gather the requested action and flags. --on, --off and
# --status are mutually exclusive; --force only modifies --on.
# --status are mutually exclusive; --force only modifies --on and --off.
local action="" force=0
while true; do
case $1 in
@@ -86,11 +100,14 @@ settrace()
printf "Try to activate backtrace display for script debugging.\n\n"
printf "Options:\n"
printf "\t--on\t\tActivate backtrace generation\n"
printf "\t--force\t\tForce replacement of existing trap (use with --on)\n"
printf "\t--force\t\tReplace (with --on) or remove (with --off) an ERR trap\n"
printf "\t\t\tnot installed by settrace\n"
printf "\t--off\t\tDeactivate backtrace generation\n"
printf "\t--status\tDisplay whether the ERR trap is set\n\n"
printf "That function active a trap event on error. If the script you want to\n"
printf "debug overload the ERR bash trap, it will not work.\n"
printf "That function activates a trap event on error. If the script you want to\n"
printf "debug overloads the ERR bash trap, it will not work.\n\n"
printf "To activate it at every interactive startup, set SETTRACE_DEFAULT_ENABLED=1\n"
printf "in the [debug] section of the profile configuration.\n"
return 0
;;
--on|--off|--status)
@@ -117,25 +134,48 @@ settrace()
done
# Second pass: evaluate the current trap state, then apply the action.
local status="off"
[[ -n $(trap -p ERR) ]] && status="on"
# "trap -p ERR" prints the exact command line that re-creates the trap, so
# comparing it tells our own trap ("on") from one set by someone else
# ("foreign"); empty output means no trap at all ("off").
# Limitation: bash hides the ERR trap from code running in a function (and
# its subshells) unless errtrace is set. Our own trap is always seen since
# settrace sets errtrace with it, but a foreign trap set without errtrace is
# reported as "off" (and can then be replaced without --force).
local status="off" current
current=$(trap -p ERR)
if [[ -n $current ]]; then
status="foreign"
[[ $current == "trap -- 'error' ERR" ]] && status="on"
fi
case $action in
on)
if [[ $status == "on" && $force -eq 0 ]]; then
disp E "ERR signal trap is already set. Use --force to replace it."
if [[ $status == "on" ]]; then
disp W "ERR signal trap is already set."
return 0
fi
if [[ $status == "foreign" && $force -eq 0 ]]; then
disp E "Another ERR signal trap is already set. Use --force to replace it."
return 1
fi
# errtrace makes functions and subshells inherit the ERR trap
set -o errtrace
trap "error" ERR
trap 'error' ERR
;;
off)
[[ $status != "on" ]] && disp W "ERR signal trap is already unset!"
if [[ $status == "foreign" && $force -eq 0 ]]; then
disp E "The ERR signal trap was not set by settrace. Use --force to remove it."
return 1
fi
[[ $status == "off" ]] && disp W "ERR signal trap is already unset!"
trap - ERR
set +o errtrace
;;
status)
disp I "Trap signal on error is ${status}."
case $status in
foreign) disp I "Trap signal on error is set, but not by settrace." ;;
*) disp I "Trap signal on error is ${status}." ;;
esac
;;
"")
disp E "No action specified, use \"settrace --help\" to display usage."
@@ -147,4 +187,22 @@ export -f settrace
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Optional trace activation at startup (SETTRACE_DEFAULT_ENABLED, default 0).
# Interactive shells only: this file is sourced by non-interactive shells too,
# where an ERR trap would add noise to scripts. [[ $- == *i* ]] is used because
# profile.sh sets INTERACTIVE only after the modules are loaded.
# disp is not defined yet (modules load alphabetically), so the trap is set only
# when none exists, a path that never calls disp. An existing trap is left alone.
# Written as "if" so the sourcing status stays 0 when nothing is done.
case "${SETTRACE_DEFAULT_ENABLED:-0}" in
1|[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn])
if [[ $- == *i* && -z $(trap -p ERR) ]]; then
settrace --on
fi
;;
esac
# ------------------------------------------------------------------------------
# EOF
+1 -1
View File
@@ -176,7 +176,7 @@ disp()
local _c=${message:_i:1}
_i=$(( _i + 1 ))
line+=$_c
(( line_vis++ ))
(( ++line_vis ))
[[ $_c == ' ' ]] && last_space=${#line}
if (( line_vis > width )); then
if (( last_space > 0 )); then
+2 -2
View File
@@ -234,7 +234,7 @@ pinfo()
[[ "$cpu_model" == "unknown" ]] && cpu_model="${_cpu_line#*: }"
;;
"processor"*)
(( cpu_threads++ ))
cpu_threads=$(( cpu_threads + 1 ))
;;
"physical id"*)
_cur_phys="${_cpu_line#*: }"
@@ -269,7 +269,7 @@ pinfo()
# --- Process count (glob over numeric /proc entries) ---
local proc_count=0 _pdir
for _pdir in /proc/[0-9]*/; do
[[ -d "$_pdir" ]] && (( proc_count++ ))
[[ -d "$_pdir" ]] && proc_count=$(( proc_count + 1 ))
done
# --- Shell and terminal ---
+1 -1
View File
@@ -295,7 +295,7 @@ isipv6()
IFS=':' read -ra parts <<< "$seg"
for part in "${parts[@]}"; do
[[ $part =~ ^[0-9a-fA-F]{1,4}$ ]] || { ok=0; break; }
(( total++ ))
total=$(( total + 1 ))
done
(( ok )) || break
done
+1 -1
View File
@@ -118,7 +118,7 @@ load_theme()
local _lth_line _lth_key _lth_value _lth_varname _lth_lineno=0
while IFS= read -r _lth_line || [[ -n "$_lth_line" ]]; do
((_lth_lineno++))
_lth_lineno=$(( _lth_lineno + 1 ))
_lth_line="${_lth_line%$'\r'}" # strip CR
_lth_line="${_lth_line#"${_lth_line%%[![:space:]]*}"}" # ltrim
_lth_line="${_lth_line%"${_lth_line##*[![:space:]]}"}" # rtrim
+2 -2
View File
@@ -234,7 +234,7 @@ genpwd()
break
fi
((attempts++))
attempts=$(( attempts + 1 ))
done
done
@@ -256,7 +256,7 @@ genpwd()
break
fi
((attempts++))
attempts=$(( attempts + 1 ))
done
done
+3 -3
View File
@@ -300,7 +300,7 @@ _rain_engine()
if ((100 * RANDOM / 32768 < falling_odd)); then
if ((++rains[idx + 1] > term_height)); then
rains=("${rains[@]:0:idx}" "${rains[@]:idx+num_rain_metadata:num_rains*num_rain_metadata}")
((num_rains--))
num_rains=$(( num_rains - 1 ))
continue
fi
fi
@@ -359,7 +359,7 @@ _rain_engine()
X=$((term_width * RANDOM / 32768 + 1))
Y=$((1 - drop_length))
rains=("${rains[@]}" "$X" "$Y" "$rain_drop" "$drop_color" "$drop_length")
((num_rains++))
num_rains=$(( num_rains + 1 ))
fi
do_render
@@ -684,7 +684,7 @@ rainbow()
for ((col = 0; col < term_width; col++)); do
idx=$((col - offset))
while ((idx < 0)); do
((idx += palette_width))
idx=$(( idx + palette_width ))
done
idx=$((idx % palette_width))
row_line+="${RAINBOW_BG_PALETTE[idx]} "