13

I have a bunch of generic cleanup code that needs to be done whenever a certain bash script exits, whether it exited normally or was interrupted. I figured I'd use the trap "..." EXIT pseudosignal to achieve this.

In addition to the generic cleanup stuff, there's also one piece of specific cleanup that should only be done if the script completes normally. I thought I could trigger this by having the 'trap' block test a variable, like so:

#!/bin/bash
done=false;
trap "{        
           #generic cleanup code goes here.
           if $done
           then
               #cleanup to be done only on completion goes here.
               echo Test;
           fi
       }" EXIT
#main script goes here
done=true;

However, this doesn't work. Running the following code will never echo "Test". Adding an explicit exit call after done=true; doesn't change anything. What am I missing?

Cheers!

Jolta
  • 2,620
  • 1
  • 29
  • 42
Zac B
  • 3,796
  • 3
  • 35
  • 52

1 Answers1

22

The trap is being interpolated, and is using the value of $done at the time the trap is being defined rather than when it is executed. You can use single quotes around the trap definition, or define a function. Defining a function is probably cleaner:

#!/bin/sh
done=false
cleanup() { if test "$done" = true; then echo Test; fi; }
trap cleanup EXIT
done=true

This works because the expansion of variables in the function is deferred until the function is called, rather than when the function is defined.

William Pursell
  • 204,365
  • 48
  • 270
  • 300
  • I wonder is it signal safe. Say if signal arrives between last command of initialization and `done=true` assignment. – reddot Nov 02 '21 at 23:45
  • @reddot That is a valid concern, but I'm not sure it's correct to use the phrase "signal safe". It's just a race condition. "signal safe" refers to something different. (eg functions that are non-reentrant) – William Pursell Nov 03 '21 at 09:56
  • I agree with your terminology, it's better to use "race condition" phrase here. BTW I have opened a new question about the case -- https://unix.stackexchange.com/questions/676188/shell-traps-signals-and-conditional-cleanup . Maybe you have any ideas to share. – reddot Nov 04 '21 at 18:14
  • Why doesn't it work with `while true` like `i=0; cleanup(){echo $i;}; trap cleanup EXIT; while true; do i=$(($i+1)); done` ?? – Mamdouh Saeed Apr 14 '23 at 20:14
  • @MamdouhSaeed It does work, but your shell may not execute the trap on EXIT when it receives SIGINT. Try `trap cleanup EXIT INT` (and note that some shells may execute `cleanup` twice in response to SIGINT) – William Pursell Apr 14 '23 at 20:25
  • I really tested it with many signals `trap cleanup 1 2 3 4 6 9` and it echo only `0` ignoring the increment by `while true` – Mamdouh Saeed Apr 14 '23 at 20:49
  • @MamdouhSaeed Interesting; which shell? – William Pursell Apr 14 '23 at 21:26
  • 1
    @MamdouhSaeed It works for me in `5.1.16(1)`, whether invoked as `bash` or `sh`. Perhaps I misunderstand how you are sending the signal. Try adding `echo $i` to the body of the do-loop to verify that it is incrementing. Perhaps your example case is simplified, and `i` is incrementing in a subshell (is the `while loop` in a pipeline?) – William Pursell Apr 15 '23 at 12:05