How do I find the line number in Bash when an error occured?
Solution 1
Rather than use your function, I'd use this method instead:
$ cat yael.bash
#!/bin/bash
set -eE -o functrace
file1=f1
file2=f2
file3=f3
file4=f4
failure() {
local lineno=$1
local msg=$2
echo "Failed at $lineno: $msg"
}
trap 'failure ${LINENO} "$BASH_COMMAND"' ERR
cp -- "$file1" "$file2"
cp -- "$file3" "$file4"
This works by trapping on ERR and then calling the failure()
function with the current line number + bash command that was executed.
Example
Here I've not taken any care to create the files, f1
, f2
, f3
, or f4
. When I run the above script:
$ ./yael.bash
cp: cannot stat ‘f1’: No such file or directory
Failed at 17: cp -- "$file1" "$file2"
It fails, reporting the line number plus command that was executed.
Solution 2
In addition to LINENO
containing the current line number, there are the BASH_LINENO
and FUNCNAME
(and BASH_SOURCE
) arrays that contain the function names and line numbers they're called from.
So you could do something like this:
#!/bin/bash
error() {
printf "'%s' failed with exit code %d in function '%s' at line %d.\n" "${1-something}" "$?" "${FUNCNAME[1]}" "${BASH_LINENO[0]}"
}
foo() {
( exit 0 ) || error "this thing"
( exit 123 ) || error "that thing"
}
foo
Running that would print
'that thing' failed with exit code 123 in function 'foo' at line 9.
If you use set -e
, or trap ... ERR
to automatically detect errors, note that they have some caveats. It's also harder to include a description of what the script was doing at the time (as you did in your example), though that might be more useful to a regular user than just the line number.
See e.g. these for the issues with set -e
and others:
- Why does set -e not work inside subshells with parenthesis () followed by an OR list ||?
- bash -e exits when let or expr evaluates to 0
- BashFAQ 105: Why doesn't set -e (or set -o errexit, or trap ERR) do what I expected?
Solution 3
Bash has a built-in variable $LINENO
which is replaced by the current line number when in a statement, so you can do
in_case_fail $? "at $LINENO: cp $file1 $file2"
You could also try using trap ... ERR
which runs when a command fails (if the result is not tested). Eg:
trap 'rc=$?; echo "error code $rc at $LINENO"; exit $rc' ERR
Then if a command like cp $file1 $file2
fails you will get the error message with the line number and an exit. You will also find the command in error in variable $BASH_COMMAND
(though not any redirections etc.).
Solution 4
I decided to use the following, which includes a stack trace as list of fns [list of line numbers]
if the script is in a function or the script line number otherwise. Thanks also go to previous answers, which I expanded on after some experimentation to handle both fn based and non fn based scripts.
set -eE -o functrace
failure() {
local lineno=$2
local fn=$3
local exitstatus=$4
local msg=$5
local lineno_fns=${1% 0}
if [[ "$lineno_fns" != "0" ]] ; then
lineno="${lineno} ${lineno_fns}"
fi
echo "${BASH_SOURCE[1]}:${fn}[${lineno}] Failed with status ${exitstatus}: $msg"
}
trap 'failure "${BASH_LINENO[*]}" "$LINENO" "${FUNCNAME[*]:-script}" "$?" "$BASH_COMMAND"' ERR
So a simple command failure, looks like:
/tmp/b:script[32] Failed with status 1: cp fred john
And nested functions (where hello1 calls hello2):
/tmp/b:hello2 hello1 main[24 19 29] Failed with status 1: cp john 22
I report exit status for the odd times it gives you extra info, and unlike everyone else I want the full pathname of the script.
Further work will be needed to report exit due to signals.
Related videos on Youtube
yael
Updated on September 18, 2022Comments
-
yael over 1 year
How do you find the line number in Bash where an error occurred?
Example
I create the following simple script with line numbers to explain what we need. The script will copy files from
cp $file1 $file2 cp $file3 $file4
When one of the
cp
commands fail then the function will exit with exit 1. We want to add the ability to the function to also print the error with the line number (for example, 8 or 12).Is this possible?
Sample script
1 #!/bin/bash 2 3 4 function in_case_fail { 5 [[ $1 -ne 0 ]] && echo "fail on $2" && exit 1 6 } 7 8 cp $file1 $file2 9 in_case_fail $? "cp $file1 $file2" 10 11 12 cp $file3 $file4 13 in_case_fail $? "cp $file3 $file4" 14
-
slm almost 6 yearsRelated - stackoverflow.com/questions/24398691/…
-
Rolf almost 6 yearsYou could use
set -x
and/orset -v
to trace what has been executed. Not exactly what you asked for but it will probably be helpful, too.
-
-
jmrah almost 4 yearsAny particular reason you're using
set -o functrace
in your example? -
slm almost 4 years@jrahhali - takke a look at bash's man page on
functrace
. Allows any debugging enabled via calling to propagate to the function. -
jmrah almost 4 yearsI'm familiar with it. I was just confused because you weren't using any DEBUG or RETURN traps in your example, so thought I was missing some cool functionality of functrace. But, it sounds like your reason for including it is in case the caller has a DEBUG or RETURN trap defined?
-
slm almost 4 years@jrahhali - correct, that's exactly why.
-
kdubs over 3 yearsdoes ${1-something} do the same thing as ${1:-something} ? I'm having trouble finding help on the - without the :
-
ilkkachu over 3 years@kdubs, almost.
${var-default}
inserts the default value only ifvar
is unset,${var:-default}
also if it's set to the empty string. Try e.g.f() { echo "a: ${1-foo} b: ${1:-foo}"; };
and call it asf ""
or justf
with no arguments. The POSIX text has a nice table of the different cases: pubs.opengroup.org/onlinepubs/9699919799/utilities/… (they use "null" for the empty string). -
kdubs over 3 yearsthanks. maybe my man page is out of date. couldn't find this in it. (or I could be blind...)
-
ilkkachu over 3 years@kdubs, it's a bit hidden in Bash manuals. It says: "When not performing substring expansion, using the forms documented below (e.g., :-), bash tests for a parameter that is unset or null. Omitting the colon results in a test only for a parameter that is unset." and then lists just the versions with colons. Here in the online manual, the man page should have similar text.
-
kdubs over 3 yearsthanks. I can see it now. I was just kind of glossing over that bit before. it didn't make sense before. now it does.