5

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.

Jolta
  • 2,620
  • 1
  • 29
  • 42
Steve Amerige
  • 1,309
  • 1
  • 12
  • 28

2 Answers2

2

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.

Steve Amerige
  • 1,309
  • 1
  • 12
  • 28
  • You didn't even use `$(...)` in the first form, you used a plain subshell. With `$(...)` you get the same as with backticks. – gniourf_gniourf Dec 30 '16 at 12:33
  • @gniourf_gniourf Yeah, I just did the same test myself and saw the same thing as you did. I've updated the solution! – Steve Amerige Dec 30 '16 at 12:50
  • Yes, I was wrong. I sent a correction to the email thread: https://lists.gnu.org/archive/html/bug-bash/2016-12/msg00122.html I think this is a bug, since `[[ ... ]]` and `(( ... ))` have the same problem (both will report an incorrect line number when executing inside a function and an error is triggered). Try with `[[ a = b ]]` or `(( 0 ))`. Both will trigger the ERR trap, with an improper LINENO value. – dualbus Dec 30 '16 at 18:23
  • At least the exceptions documented in the Bash man page (`if`, `elif`, `&&`, `||`, and `!`) are working as expected. I am glad you mentioned the `[[ ... ]]` and `(( ... ))` blocks (in isolation) because I frequently use the idiom `[[ ... ]] && ` instead of `if [[ ... ]]; then ; fi` blocks. **Is there any way to prevent `ERR` traps for `[[ ... ]]` and `(( ... ))` code blocks (in isolation)?** If no, this seems to tell me that I must change my practices to use `if` or other similar controls to avoid `ERR` traps I don't want. – Steve Amerige Dec 31 '16 at 13:07
  • 2
    Thanks to all who have participated. Please see http://lists.gnu.org/archive/html/help-bash/2017-01/msg00003.html for Chet Ramey's confirmation that this will be fixed in the next version of Bash. Marking this as the solution, confirming that a workaround is to use command substitution instead. – Steve Amerige Jan 05 '17 at 17:57
1
#!/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...

Dario
  • 2,673
  • 20
  • 24
  • This solution doesn't scale nicely. The example I've provided is minimal to demonstrate the problem. My example also doesn't deal with nesting of subprocesses, multi-line subprocesses, etc. In a piece of complex scripting, I wouldn't want to add this in front of each and every line that could possibly return a non-zero status. I do appreciate the answer, but it isn't practical for me. – Steve Amerige Dec 27 '16 at 15:04
  • You have the trap on the upper level. The subshell knows it’s at LINENO 20: try executing `(eval 'echo ${LINENO}'; exit 73)` instead of the simple `(exit 73)`; if you don’t protect that with single quotes and `eval` at the the right moment, you might miss this fact. However, when the trap gets executed, this info has been lost. Either you have the trap inherited by the subshell (I don’t know how to do that), or you find a way for the subshell to pass it up: as soon as the subshell exits, the caller forgets where it called it... – Dario Dec 27 '16 at 15:15
  • This has the same problem... it requires decoration of the code. Imagine that inside `main` after the trap, I'm calling function `f` and this function executes code in a subshell that has a non-zero return status. _Any decoration_ of code is inherently non-scalable. I'm needing a solution that is limited to the invocation of the `trap` command itself. In this regard, the `trap` command can have arguments of any complexity and even call other functions. I'm hoping for a solution here. Again, thanks for your feedback. – Steve Amerige Dec 27 '16 at 15:22
  • We are out of luck: [traps are never inherited by subshells, as per POSIX standard](http://unix.stackexchange.com/questions/282722/is-trap-inherited-by-a-subshell) ... This means you will have to set them explicitly for each subshell (not scalable) or that you should define a protocol by which the info is passed up... – Dario Dec 27 '16 at 15:22
  • POSIX compliance is not required. I actually already have Bash code working that can handle multiple nesting levels of subshells and performs traps as one would want handled. That's why I specifically mentioned that I don't require POSIX compliance. **Background:** I am implementing try/catch in Bash with variable persistence. The **try** block is in a subshell. It all works beautifully, including variable persistence, _except_ that I seem only to be able to know the beginning line and ending line of the try block. While I can live with this limitation, I am hoping for an improvement. – Steve Amerige Dec 27 '16 at 15:28
  • I agree that any decoration of code is inherently non-scalable. I don’t know the context where you will run all that. The only info a subshell passes up to its caller is the return code... if you limit yourself to lexical contexts with no more than 255 lines, you can use the return code to communicate with your parent, and this is as small a decoration as you can get... – Dario Dec 27 '16 at 15:31
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/131607/discussion-between-steve-amerige-and-dario). – Steve Amerige Dec 27 '16 at 15:32