Where does the exit status go after trap/return?

2019-04-29 23:19发布

I was playing around with using trap inside a function because of this question, and came up with this secondary question. Given the following code:

d() {
    trap 'return' ERR
    false
    echo hi
}

If I run d, the trap causes the shell to return from the function without printing 'hi'. So far so good. But if I run it a second time, I get a message from the shell:

-bash: return: can only `return' from a function or sourced script

At first, I assumed this meant the ERR sig was happening twice: Once when false gave a nonzero exit status (inside the function) and again when the function itself returned with a nonzero exit status (outside the function). But that hypothesis doesn't hold up against this test:

e() {
    trap 'echo +;return' ERR
    false
    echo hi
}

If I run the above, no matter how often I run it, I no longer get the can only return from a function or sourced script warning from bash. Why does the shell treat a compound command different from a simple command in a trap arg?

My goal was to maintain the actual exit status of the command that caused the function to exit, but I think whatever is causing the above behavior also makes capturing the exit status complicated:

f() {
    trap '
        local s=$?
        echo $s
        return $s' ERR
    false
    echo hi
}

bash> f; echo $?
1
0

Wat? Can someone please explain why $s expands to two different values here and, if it turns out to be the same cause, the above differentiation between return and echo +; return?

1条回答
看我几分像从前
2楼-- · 2019-04-30 00:10

Your first conclusion was right: the ERR sig was happening twice.

During the first execution of 'd', you define a trap globally. This affect next commands (the current call of d is not affected). During the second execution of 'd', you define a trap again (not very useful), the call of 'false' fails, so we execute the handler defined by the trap. Then we return in the parent shell where 'd' fails too, so we execute the trap again.

Just a remark. ERR can be given as 'sigspec' argument, but ERR is not a signal ;-) From the manual of BASH:

If a sigspec is ERR, the command arg is executed whenever a sim‐
ple command has a non-zero exit status, subject to the following
conditions. [...]
These are  the  same  conditions obeyed by the errexit option.

With the function 'e', the ERR handler executes the 'echo' command that succeeds. That's why the 'e' function doesn't fail, that's why the ERR handler is not called twice in this case.

If you try "e; echo $?" you will read "0".

Then I tried your 'f' function. I observed the same behavior (and I was surprised). The cause is NOT a bad expension of "$s". If you try to hardcode a value, you should observe the argument given to the 'return' statement is ignored when it is executed in by the trap handler.

I don't know if it is a normal behavior or if it's a bug of BASH... Or maybe a trick to avoid an infinite loop in the interpreter :-)

By the way, it's not a good use of trap in my opinion. We can avoid side effect of trap by creating a sub-shell. In this case we avoid the error in the parent shell and we keep the exitcode of the inner function:

g() (
    trap 'return' ERR
    false
    echo hi
)
查看更多
登录 后发表回答