Is it possible to get what line the ERR signal was sent from?
Yes, LINENO
and BASH_LINENO
variables are supper useful for getting the line of failure and the lines that lead up to it.
Or maybe I'm going at this all wrong?
Nope, just missing -q
option with grep...
echo hello | grep -q "asdf"
... With the -q
option grep
will return 0
for true
and 1
for false
. And in Bash it's trap
not Trap
...
trap "_func" ERR
... I need a native solution...
Here's a trapper that ya might find useful for debugging things that have a bit more cyclomatic complexity...
failure.sh
## Outputs Front-Mater formatted failures for functions not returning 0
## Use the following line after sourcing this file to set failure trap
## trap 'failure "LINENO" "BASH_LINENO" "${BASH_COMMAND}" "${?}"' ERR
failure(){
local -n _lineno="${1:-LINENO}"
local -n _bash_lineno="${2:-BASH_LINENO}"
local _last_command="${3:-${BASH_COMMAND}}"
local _code="${4:-0}"
## Workaround for read EOF combo tripping traps
if ! ((_code)); then
return "${_code}"
fi
local _last_command_height="$(wc -l <<<"${_last_command}")"
local -a _output_array=()
_output_array+=(
'---'
"lines_history: [${_lineno} ${_bash_lineno[*]}]"
"function_trace: [${FUNCNAME[*]}]"
"exit_code: ${_code}"
)
if [[ "${#BASH_SOURCE[@]}" -gt '1' ]]; then
_output_array+=('source_trace:')
for _item in "${BASH_SOURCE[@]}"; do
_output_array+=(" - ${_item}")
done
else
_output_array+=("source_trace: [${BASH_SOURCE[*]}]")
fi
if [[ "${_last_command_height}" -gt '1' ]]; then
_output_array+=(
'last_command: ->'
"${_last_command}"
)
else
_output_array+=("last_command: ${_last_command}")
fi
_output_array+=('---')
printf '%s\n' "${_output_array[@]}" >&2
exit ${_code}
}
... and an example usage script for exposing the subtle differences in how to set the above trap for function tracing too...
example_usage.sh
#!/usr/bin/env bash
set -E -o functrace
## Optional, but recommended to find true directory this script resides in
__SOURCE__="${BASH_SOURCE[0]}"
while [[ -h "${__SOURCE__}" ]]; do
__SOURCE__="$(find "${__SOURCE__}" -type l -ls | sed -n 's@^.* -> \(.*\)@\1@p')"
done
__DIR__="$(cd -P "$(dirname "${__SOURCE__}")" && pwd)"
## Source module code within this script
source "${__DIR__}/modules/trap-failure/failure.sh"
trap 'failure "LINENO" "BASH_LINENO" "${BASH_COMMAND}" "${?}"' ERR
something_functional() {
_req_arg_one="${1:?something_functional needs two arguments, missing the first already}"
_opt_arg_one="${2:-SPAM}"
_opt_arg_two="${3:0}"
printf 'something_functional: %s %s %s' "${_req_arg_one}" "${_opt_arg_one}" "${_opt_arg_two}"
## Generate an error by calling nothing
"${__DIR__}/nothing.sh"
}
## Ignoring errors prevents trap from being triggered
something_functional || echo "Ignored something_functional returning $?"
if [[ "$(something_functional 'Spam!?')" == '0' ]]; then
printf 'Nothing somehow was something?!\n' >&2 && exit 1
fi
## And generating an error state will cause the trap to _trace_ it
something_functional '' 'spam' 'Jam'
The above where tested on Bash version 4+, so leave a comment if something for versions prior to four are needed, or Open an Issue if it fails to trap failures on systems with a minimum version of four.
The main takeaways are...
set -E -o functrace
trap 'failure "LINENO" "BASH_LINENO" "${BASH_COMMAND}" "${?}"' ERR
Single quotes are used around function call and double quotes are around individual arguments
References to LINENO
and BASH_LINENO
are passed instead of the current values, though this might be shortened in later versions of linked to trap, such that the final failure line makes it into output
Values of BASH_COMMAND
and exit status ($?
) are passed, first to get the command that returned an error, and second for ensuring that the trap does not trigger on non-error statuses
And while others may disagree I find it's easier to build an output array and use printf for printing each array element on it's own line...
printf '%s\n' "${_output_array[@]}" >&2
... also the >&2
bit at the end causes errors to go where they should (standard error), and allows for capturing just errors...
## ... to a file...
some_trapped_script.sh 2>some_trapped_errros.log
## ... or by ignoring standard out...
some_trapped_script.sh 1>/dev/null
As shown by these and other examples on Stack Overflow, there be lots of ways to build a debugging aid using built in utilities.
bashdb
. It seems that the first argument totrap
can contain variables that are evaluated in the desired context. Sotrap 'echo $LINENO' ERR'
should work.trap 'echo $LINENO' ERR
. The first argument totrap
is the entireecho $LINENO
hardquoted. This is in bash.trap 'echo $LINENO' ERR
, with single quotes, not double quotes. With the command you wrote,$LINENO
is expanded when line 2 is parsed, so the trap isecho 2
(or ratherECHO 2
, which would outputbash: ECHO: command not found
).