Bash Trap: How to Get Line Number of a Subprocess with Non-Zero Status

769 views Asked by At

For the Bash program:

 1  #!/bin/bash
 2  
 3  trapinfo()
 4  {
 5     echo "=== Trap Info: Status=$? LINENO=$@ A=$A"
 6  }
 7  
 8  main()
 9  {
10     trap 'trapinfo $LINENO -- ${BASH_LINENO[*]}' ERR
11  
12     set -e
13     set -E
14     set -o errtrace
15     shopt -s extdebug
16  
17     local -g A=1
18  
19     # false        # If uncommented, LINENO would be 19
20     (exit 73)      # LINENO is 9. How can I get 20 instead?
21  
22     A=2
23  }
24  
25  main

with the output:

=== Trap Info: Status=73 LINENO=9 -- 25 0 A=1

I am looking to find a way to have it so that subshells that exit with a non-zero status are caught by trap and show the line number of the failing subshell. In the example above, I am looking for line 20 as the result. I note that if the error is not in a subshell, I do get the wanted line number (see false above).

I've tried moving the trap to just before the subshell to check if the line number 9 was actually connected to the trap invocation, but I get the same results. I've also tried placing the set and shopt entries into the subshell as well--again with no change in behavior.

Environment:

  • bash-4.2.46-21.el7_3.x86_64: this is a requirement, but POSIX compliance is NOT required. I am also interested in later Bash releases (4.2+).
  • CentOS 7+: while mainly interested in CentOS, I will eventually need this for Bash scripts that are deployed on Ubuntu 16.04+ and CentOS 6 as well.

Is it possible to get the line number of the subprocess that returns the non-zero status? If it is not possible, is there any documentation to that effect? If a solution exists, it should scale well without unnecessary decoration in the code.

2

There are 2 answers

5
Steve Amerige On BEST ANSWER

I asked the Bash e-mail group help-bash for help on this. Eduardo Bustamante provided the following two code blocks to point to a possible bug in Bash that is at the root of the difficulty here.

First, an even simpler demonstration of the problem:

     1  #!/bin/bash
     2  shopt -s extdebug
     3  main() {
     4  trap 'echo $LINENO' ERR
     5  (exit 17)
     6  }
     7  main

The above has the output of 3.

Next, consider changing $(...) to `...`:

     1  #!/bin/bash
     2  shopt -s extdebug
     3  main() {
     4  trap 'echo $LINENO' ERR
     5  `exit 17`
     6  }
     7  main

The above has the output of 5.

Now, I did my own test:

     1  #!/bin/bash
     2  shopt -s extdebug
     3  main() {
     4  trap 'echo $LINENO' ERR
     5  $(exit 17)
     6  }
     7  main

This also has the desired output of 5.

So, the resolution of this question seems to be that this is a bug in Bash with the workaround being to use command substitution instead of just a subshell ().

Again, my thanks to Eduardo Bustamante for his insight. I'll wait a few days to see if he posts a solution here to accept his answer; otherwise, I'll mark this as the accepted answer and give thanks to him.

7
Dario On
#!/bin/bash

trapinfo()
{
   echo "=== Trap Info: Status=$? LINENO=$@ A=$A"
}

main()
{
   trap 'trapinfo $LINENO $SAVE_IT -- ${BASH_LINENO[*]}' ERR

   set -e
   set -E
   set -o errtrace
   shopt -s extdebug

   local -g A=1

   # false        # If uncommented, LINENO would be 19
   SAVE_IT=$LINENO && (exit 73)     # LINENO is magic, but a custom variable isn’t

   A=2
}

main

Maybe I am missing something, but perhaps this will work for you...