22

I tried a following script:

#!/bin/bash
trap 'echo "touching a file" && touch $FILE' EXIT

foo1(){
        echo "foo1"
}
foo(){
        echo "foo"
        export FILE=${FILE:-/tmp/file1}
}
(foo1)
foo

The output for the above script was:

[root@usr1 my_tests]# ./test.sh
foo1
foo
touching a file

However I was expecting trap to be called on exit from foo1 as well, which is called in a subshell.

  • Is this expected?
  • Is trap inherited by a subshell?
  • If yes, then in what case is trap inherited by a subshell?
Peter David Carter
  • 502
  • 2
  • 7
  • 29

4 Answers4

16

Trap handlers are never inherited by subshells. This is specified by POSIX:

When a subshell is entered, traps that are not being ignored are set to the default actions.

Note that ignored signals (trap '' SIGFOO) remain ignored in the subshell (and in external programs launched by the shell, too).

Gilles 'SO- stop being evil'
  • 807,993
  • 194
  • 1,674
  • 2,175
  • 5
    In bash you can `set -E` in order to have subshells inherit traps, but it is REALLY tricky to get right (at least in my experience). – dragon788 Sep 26 '18 at 02:11
  • 2
    I don't know if this works for all traps. I know it works for ERR – yosefrow Aug 21 '19 at 11:38
  • @dragon788 Re: "it is REALLY tricky to get right": can you please elaborate? If it is too long, then in a few words. – pmor Nov 25 '22 at 10:44
  • I see, the "that are not being ignored" part is important. – KFL Jul 18 '23 at 23:42
8

trap is not propogated to subshells but some ways allow the subshell to report the traps of the parent shell and others don't. I did some tests on macos with bash.

GNU bash, version 4.4.12(1)-release (x86_64-apple-darwin16.3.0):

trap 'echo hello' EXIT
trap # trap -- 'echo hello' EXIT
echo "$(trap)" # trap -- 'echo hello' EXIT
trap | cat # trap -- 'echo hello' EXIT
(trap) | cat # trap -- 'echo hello' EXIT
cat < <(trap) # empty
cat <<< "$(trap)" # empty
bash -c 'trap' # empty
trap & # trap -- 'echo hello' EXIT

GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin16):

trap 'echo hello' EXIT
trap # trap -- 'echo hello' EXIT
echo "$(trap)" # trap -- 'echo hello' EXIT
trap > >(cat) # trap -- 'echo hello' EXIT
trap | cat # empty
(trap) | cat # empty
cat < <(trap) # empty
cat <<< "$(trap)" # empty
bash -c 'trap' # empty
trap & # empty

This is good to know that trap_output="$(trap)" will work to capture trap output. I can't think of any other way to do it if that didn't work besides doing trap >trap_output_file to output it to a file (fifo won't work in bash 3.2.57) and then reading it back in with trap_output="$(<trap_output_file)"

fifo won't work in bash 3.2.57 because trap & is empty for bash 3.2.57 but not bash 4.4.12

GNU bash, version 4.4.12(1)-release (x86_64-apple-darwin16.3.0):

mkfifo /tmp/fifo; trap >/tmp/fifo & trap_output=$(</tmp/fifo); rm -f /tmp/fifo; echo "$trap_output"
# trap -- 'echo hello' EXIT

mkfifo /tmp/fifo; trap_output=$(</tmp/fifo) & trap >/tmp/fifo; rm -f /tmp/fifo; echo "$trap_output"
# empty because trap_output=$(</tmp/fifo) sets the variable in a subshell

GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin16):

mkfifo /tmp/fifo; trap >/tmp/fifo & trap_output=$(</tmp/fifo); rm -f /tmp/fifo; echo "$trap_output"
# empty because trap >/tmp/fifo & is empty since it uses trap &

mkfifo /tmp/fifo; trap_output=$(</tmp/fifo) & trap >/tmp/fifo; rm -f /tmp/fifo; echo "$trap_output"
# empty because trap_output=$(</tmp/fifo) sets the variable in a subshell
dosentmatter
  • 498
  • 5
  • 12
0

trap definitions are not propagated to sub-shells.

Verify by:

trap "echo bla" 1 2 3

(trap)

schily
  • 18,806
  • 5
  • 38
  • 60
  • 4
    Many shells handle `(trap)` as a special case, so that the subshell can report (but not actually use) the traps of the parent shell. So that test isn't always reliable. – JigglyNaga May 12 '16 at 10:52
  • It works with the Bourne Shell and it's derivates: `ksh88`, `bosh` (schily Bourne Shell) and `heirloom-sh`. You are correct: `ksh93` behaves differently. – schily May 12 '16 at 10:57
  • It doesn't work in bash, which the script in question uses. – JigglyNaga May 12 '16 at 11:01
  • Well, it **works** in bash: `bash` does not output anything if you call `(trap)`. – schily May 12 '16 at 11:06
  • @schily the example has an extra quote. Also, in my installation, both bash44 and bash 5+ report traps on `(trap)` – usretc Apr 25 '21 at 10:46
  • @usretc type fixed, thank you! BTW: this seems to be a ksh93 bug that has been copied. It does not apply to other shells -not even to ksh88. – schily Apr 25 '21 at 11:56
0

Although calling trap in a subshell shows the parent's traps, they are not executed in the subshell. You can set a new trap in the subshell.

To test this, create a script with the following code, run it and ctrl+c:

#!/usr/bin/bash
function sub { echo sub_before: `trap`; trap 'echo kill_sub; exit' SIGINT; echo sub_after: `trap`; while :; do sleep 1; done; }
function parent { echo parent_before: `trap`; trap 'pkill -2 -P $$; echo kill_parent; exit' SIGINT; echo parent_after: `trap`; sub & wait; }
parent

You'll see both traps being executed.

(bash 5.0.17)