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.).