How do I find the line number in Bash when an error occured?
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.
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?
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.).