4

I am using the following script:

#!/bin/bash -Eu

trap 'echo Hi' ERR

exit_failure() {
  echo "Hello, World!"
  return 1
}

sub_failure() {
  res=$(exit_failure)
}

sub_failure

It results in the following:

Hi
Hi

If I change sub_failure() to the following, however:

sub_failure() {
  local res=$(exit_failure)
}

I get no output; ERR is not trapped anymore? Why is the signal hidden? How can I trap ERR if I want to use local variables? I know I can do local res; res=$(exit_failure), but why do I have to separate both?

6
  • local is almost definitely the problem - it's not exactly a standardized feature - the shell will have its own trick at implementation, which might have unintended or non-standard side effects. It's just the risk you run, I guess. Anyway, the point of local is to not report to the parent, so, I guess it's just doing it's job really well.
    – mikeserv
    Jul 24, 2014 at 12:23
  • So the answer is that local does not report to the parent, ok. So generally speaking good style would be to always separate declaration from assignment, right? Jul 24, 2014 at 12:30
  • It's a shell-script - good style is an incredibly relative term. Probably good style doesn't blow up. Still, if I ever use it I do it with declarations. Better style is to not declare variables in current shell functions - use your positional array to the utmost. Do man set.
    – mikeserv
    Jul 24, 2014 at 12:32
  • man set does not work. Positional array, why? I just want to decrease the visibility nothing more... Jul 24, 2014 at 12:36
  • man set does not work? Wow. Anyway, it was the wrong advice. I think you want to look at return - just use $?.
    – mikeserv
    Jul 24, 2014 at 12:46

1 Answer 1

6

It's not a bug. It's actually defined behavior.

When using bash -Eux you can see what happens. (-Eu from your shebang + -x)

+ trap 'echo Hi' ERR
+ sub_failure
++ exit_failure
++ echo 'Hello, World!'
++ return 1
+++ echo Hi
+ res='Hello, World!
Hi'
++ echo Hi
Hi
++ echo Hi
Hi

When doing command substitution the trap is inherited because of the -E switch. So the "Hi" from the inherited trap triggered by the return 1 of your exit_failure() function becomes part of the value stored in ret. (This is also the case when executing the variant using local)

In Addition the res=... expression returns 1 (error) and triggers your trap (inside your sub_failure() function).

Since res=... return 1 and the result of a function is the result of the last command in the function the result of sub_failure() is also 1 (error) and your trap is triggered again after sub_failure has been executed in the main shell. So you get 2 visible "Hi"s: one for res=.... and one for sub_failure and a hidden "Hi" stored in $res.

Now for the local variant:

+ trap 'echo Hi' ERR
+ sub_failure
++ exit_failure
++ echo 'Hello, World!'
++ return 1
+++ echo Hi
+ local 'res=Hello, World!
Hi'

By definition local always returns 0 when used in a function. Causing your local res=... to evaluate to 0 (success) while still having the hidden "Hi" stored in $res. And since res=.. evaluates to 0 sub_failure also returns 0. So this time you get one "hidden" fail and two time success.

Hope this helps even if this thread is quiet old ;)

And it should also be clear why splitting local res=... in

local res
res=....

does restore the behavior of the first variant.. ? ;)

2
  • 2
    +1 even if this thread is old, great analysis
    – amdn
    Nov 22, 2016 at 23:13
  • 1
    +100 "even if this thread is old, great analysis" :) the final workaround should be put in bold, it's a very tricky case, and not so easy to google ! (and the workaround works, obviously)
    – Kevin
    Nov 24, 2022 at 8:21

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .