17

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
  • Related - stackoverflow.com/questions/24398691/… – slm Aug 12 '18 at 18:26
  • You could use set -x and/or set -v to trace what has been executed. Not exactly what you asked for but it will probably be helpful, too. – Rolf Aug 23 '18 at 8:34
27

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.

13

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:

11

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.