1

Consider this script that calls into a function that sets up a trap for the RETURN of that function:

#!/usr/bin/env bash
set -euo pipefail

function f1() {
  local temp_file
  temp_file="$(mktemp)"
  trap 'echo DELETED; rm -- "${temp_file}"' RETURN
  date > temp_file
}

function main() {
  for i in $(seq 0 2); do
    f1
  done
}

main

For some reason this prints DELETED string 4 times (instead of 3) and fails with an unbound variable error:

DELETED
DELETED
DELETED
DELETED
hack/deploy-example-server.sh: line 13: temp_file: unbound variable

Clearly, something is causing an extra execution of this trap, I'm suspecting it's the overall script's exit.

This does not happen if I eliminate function main, and just call f1 in a for-loop in the top-level script.

What's causing this behavior?

3
  • I believe it's the return from main() that causes the extra execution but the whole thing looks like a specification-by-implementation bug to me.
    – NickD
    Aug 28, 2019 at 22:11
  • The RETURN trap will be executed for all the callers through to the main script. Example: bash -c 'foo(){ bar; }; bar(){ baz; }; baz(){ trap "echo \${FUNCNAME[@]}" RETURN; }; foo'. I wasn't able to find this clearly mentioned anywhere.
    – user313992
    Aug 29, 2019 at 2:40
  • 1
    btw, if you copied my example in a file and run it as bash file, you would see that it's not a good idea to call a function main in bash.
    – user313992
    Aug 29, 2019 at 2:43

1 Answer 1

0

Any trap you set is global.

The fourth trap happens when main() returns. At this point, the local variable temp_file is no longer set. (Hence the unbound variable error.)

Consider unsetting the trap in main() after the for loop. I think you can unset the trap with trap RETURN.

Alternatively (and preferably?), consider using something other than trap.

Option 1: Append each file you want to delete to an array, and then explicitly delete those files later on.

Option 2: Wrap function f1() with another function f2(), and have f2() delete the file. Perhaps someting like the below (which I have not tested):

#! /usr/bin/env bash

set -euo pipefail

function trace() {
  #  usage:  trace  command  [arg ...]
  echo  "+  $*"  1>&2
  "$@"
}

function f1() {
  true    #  replace true with your code
}

function f2() {
  #  usage:  f2  func_name
  local func_name="$1"
  local temp_file="$(mktemp)"
  "$func_name"
  trace  rm  --  "$temp_file"
}
    
function main() {
  for i in $(seq 0 2); do
    f2  f1
  done
}

main

You must log in to answer this question.

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