Arseni Mourzenko
Arseni Mourzenko

Reputation: 52321

Why is `trap -` not working when put inside a function?

Short version

In a Bash script, I activate a trap, and later deactivate it by calling trap - EXIT ERR SIGHUP SIGINT SIGTERM. When I do the deactivation directly in the script, it works. However, when I put the exact same line of code in a Bash function, it is ignored, i.e. the trap is still activated if, later, a command returns an exit code different from zero. Why?

Long version

I have a bunch of functions to work with traps:

trap_stop()
{
    echo "trap_stop"
    trap - EXIT ERR SIGHUP SIGINT SIGTERM
}

trap_terminate()
{
    local exitCode="$?"
    echo "trap_terminate"
    trap_stop

    local file="${BASH_SOURCE[1]}"
    local stack=$(caller)
    local line="${stack% *}"

    if [ $exitCode == 0 ]; then
        echo "Finished."
    else
        echo "The initialization failed with code $exitCode in $file:${line}."
    fi

    exit $exitCode
}

trap_start()
{
    echo "trap_start"
    trap "trap_terminate $LINENO" EXIT ERR SIGHUP SIGINT SIGTERM
}

When used like this:

trap_start  # <- Trap started.

echo "Stopping traps."
trap_stop  # <- Trap stopped before calling a command which exits with exit code 2.

echo "Performing a command which will fail."
ls /tmp/missing
exit_code="$?"

echo "The result of the check is $exit_code."

I get the following output:

trap_start
Stopping traps.
trap_stop
Performing a command which will fail.
ls: cannot access '/tmp/missing': No such file or directory
trap_terminate
trap_stop
The initialization failed with code 2 in ./init:41.

Despite the fact that function deactivating the trap was called, the trap was still triggered when calling ls on a directory which doesn't exist.

On the other hand, when I replace the call to trap_stop by the actual trap - statement, like this:

trap_start

echo "Stopping traps."
trap - EXIT ERR SIGHUP SIGINT SIGTERM  # <- This statement replaced the call to `trap_stop`.

echo "Performing a command which will fail."
ls /tmp/missing
exit_code="$?"

echo "The result of the check is $exit_code."

then the output is correct, i.e. the trap is not activated and I reach the end of the script.

trap_start
Stopping traps.
Performing a command which will fail.
ls: cannot access '/tmp/missing': No such file or directory
The result of the check is 2.

Why is moving trap - to a function makes it stop working?

Upvotes: 3

Views: 2146

Answers (1)

root
root

Reputation: 6048

EDIT (courtesy of @KamilCuk): If your bash is older than 4.4, upgrade your bash, it could solve the problem.


I added some debugging to your code:

echo "Stopping traps."
trap -p
trap_stop  # <- Trap stopped before calling a command which exits with exit code 2.
trap -p

And got:

Stopping traps.
trap -- 'trap_terminate 29' EXIT
trap -- 'trap_terminate 29' SIGHUP
trap -- 'trap_terminate 29' SIGINT
trap -- '' SIGFPE
trap -- 'trap_terminate 29' SIGTERM
trap -- '' SIGXFSZ
trap -- '' SIGPWR
trap -- 'trap_terminate 29' ERR
trap_stop
trap -- '' SIGFPE
trap -- '' SIGXFSZ
trap -- '' SIGPWR
trap -- 'trap_terminate 29' ERR

As you can see, the trap - part does work, except for the ERR condition.

After some man page time:

echo "Stopping traps."
set -E
trap_stop  # <- Trap stopped before calling a command which exits with exit code 2.

yields:

trap_start
Stopping traps.
trap_stop
Performing a command which will fail.
ls: cannot access '/tmp/missing': No such file or directory
The result of the check is 2.

The relevant part of bash(1):

-E If set, any trap on ERR is inherited by shell functions, command substitutions, and commands executed in a subshell environment. The ERR trap is normally not inherited in such cases.

That said, this seems to be a bug in bash:

#!/bin/bash

t1()
{
    trap 'echo t1' ERR
}

t2()
{
    trap 'echo t2' ERR
}

t1
false
t2
false

yields:

t1
t1

whereas I'd expect at the very least:

t1
t2

Upvotes: 4

Related Questions