user108471
user108471

Reputation: 2607

How to get the real line number of a failing Bash command?

In the process of coming up with a way to catch errors in my Bash scripts, I've been experimenting with "set -e", "set -E", and the "trap" command. In the process, I've discovered some strange behavior in how $LINENO is evaluated in the context of functions. First, here's a stripped down version of how I'm trying to log errors:

#!/bin/bash

set -E
trap 'echo Failed on line: $LINENO at command: $BASH_COMMAND && exit $?' ERR

Now, the behavior is different based on where the failure occurs. For example, if I follow the above with:

echo "Should fail at: $((LINENO + 1))"
false

I get the following output:

Should fail at: 6
Failed on line: 6 at command: false

Everything is as expected. Line 6 is the line containing the single command "false". But if I wrap up my failing command in a function and call it like this:

function failure {
    echo "Should fail at $((LINENO + 1))"
    false
}
failure

Then I get the following output:

Should fail at 7
Failed on line: 5 at command: false

As you can see, $BASH_COMMAND contains the correct failing command: "false", but $LINENO is reporting the first line of the "failure" function definition as the current command. That makes no sense to me. Is there a way to get the line number of the line referenced in $BASH_COMMAND?

It's possible this behavior is specific to older versions of Bash. I'm stuck on 3.2.51 for the time being. If the behavior has changed in later releases, it would still be nice to know if there's a workaround to get the value I want on 3.2.51.

EDIT: I'm afraid some people are confused because I broke up my example into chunks. Let me try to clarify what I have, what I'm getting, and what I want.

This is my script:

#!/bin/bash

set -E
function handle_error {
    local retval=$?
    local line=$1
    echo "Failed at $line: $BASH_COMMAND"
    exit $retval
}
trap 'handle_error $LINENO' ERR

function fail {
    echo "I expect the next line to be the failing line: $((LINENO + 1))"
    command_that_fails
}

fail

Now, what I expect is the following output:

I expect the next line to be the failing line: 14
Failed at 14: command_that_fails

Now, what I get is the following output:

I expect the next line to be the failing line: 14
Failed at 12: command_that_fails

BUT line 12 is not command_that_fails. Line 12 is function fail {, which is somewhat less helpful. I have also examined the ${BASH_LINENO[@]} array, and it does not have an entry for line 14.

Upvotes: 9

Views: 9280

Answers (4)

Ahmed Kamal
Ahmed Kamal

Reputation: 11

I found that as a solution

trap 'on_error $LINENO' ERR  ## not "" because it will pass the line number of the trap

Upvotes: 0

Charles Duffy
Charles Duffy

Reputation: 295281

For bash releases prior to 4.1, a special level of awful, hacky, performance-killing hell is needed to work around an issue wherein, on errors, the system jumps back to the function definition point before invoking an error handler.

#!/bin/bash

set -E
set -o functrace
function handle_error {
    local retval=$?
    local line=${last_lineno:-$1}
    echo "Failed at $line: $BASH_COMMAND"
    echo "Trace: " "$@"
    exit $retval
}
if (( ${BASH_VERSION%%.*} <= 3 )) || [[ ${BASH_VERSION%.*} = 4.0 ]]; then
        trap '[[ $FUNCNAME = handle_error ]] || { last_lineno=$real_lineno; real_lineno=$LINENO; }' DEBUG
fi
trap 'handle_error $LINENO ${BASH_LINENO[@]}' ERR

fail() {
    echo "I expect the next line to be the failing line: $((LINENO + 1))"
    command_that_fails
}

fail

Upvotes: 12

Charles Duffy
Charles Duffy

Reputation: 295281

BASH_LINENO is an array. You can refer to different values in it: ${BASH_LINENO[1]}, ${BASH_LINENO[2]}, etc. to back up the stack. (Positions in this array line up with those in the BASH_SOURCE array, if you want to get fancy and actually print a stack trace).

Even better, though, you can just inject the correct line number in your trap:

failure() {
  local lineno=$1
  echo "Failed at $lineno"
}
trap 'failure ${LINENO}' ERR

You might also find my prior answer at https://stackoverflow.com/a/185900/14122 (with a more complete error-handling example) interesting.

Upvotes: 6

Christopher C. S. Ke
Christopher C. S. Ke

Reputation: 265

That behaviour is very reasonable.

The whole picture of the call stack provides comprehensive information whenever an error occurs. Your example had demonstrated a good error message; you could see where the an error actually occurred and which line triggered the function, etc.

If the interpreter/compiler can't precisely indicate where the error actually occurs, you could be more easily confused.

Upvotes: -3

Related Questions