- commit
- dc4e461d334f9f38a33dd1614feaab6ea8ea2969
- parent
- 25b82f88cb7d2a89263b9a5b34762f7929f72b20
- Author
- Tobias Bengfort <tobias.bengfort@posteo.de>
- Date
- 2026-07-17 11:42
rework trap post
Diffstat
| M | _content/posts/2026-07-13-trap/index.md | 47 | +++++++++++++++++++++-------------------------- |
1 files changed, 21 insertions, 26 deletions
diff --git a/_content/posts/2026-07-13-trap/index.md b/_content/posts/2026-07-13-trap/index.md
@@ -2,7 +2,7 @@ 2 2 title: Using trap for cleanup in shell scripts 3 3 date: 2026-07-13 4 4 tags: [linux]5 -1 description: "trap … EXIT does not trigger if the script is terminated by a signal"-1 5 description: "trap … EXIT does not always trigger if the script is terminated by a signal" 6 6 --- 7 7 8 8 It is great when programming languages offer robust ways to cleanup resources. @@ -36,7 +36,7 @@ There are quite a few signals that are supposed to terminate a process: 36 36 SIGHUP, SIGINT, SIGQUIT, SIGKILL, and SIGTERM. The differences between them are 37 37 [not completely obvious](https://stackoverflow.com/questions/4042201). For this 38 38 article, I will concentrate on SIGINT and SIGTERM, because they are the most39 -1 common ones. SIGINT is the one that is sent when you press `Ctrl-C`.`-1 39 common ones. SIGINT is the one that is sent when you press `Ctrl-C`. 40 40 41 41 trap's action argument is given as a string, which makes quoting a bit awkward. 42 42 For example `trap "echo '$foo'" TERM` and `trap 'echo "$foo"' TERM` are @@ -73,24 +73,22 @@ see how it behaves in practice: 73 73 Bash is closest to what I expect: EXIT should mean *every* exit, including 74 74 signals. So how can we get that behavior in all shells? 75 7576 -1 The proper solution is probably to have a separate trap for each relevant77 -1 signal that removes the trap, calls that cleanup code, and then re-raises the78 -1 signal. To avoid executing the cleanup code twice in bash we also need to reset79 -1 the EXIT handler:-1 76 A simple option is to convert the relevant signals to explicit `exit`: 80 77 81 78 ```sh 82 79 trap cleanup EXIT83 -1 trap 'cleanup; trap - INT; trap - EXIT; kill -INT $$'84 -1 trap 'cleanup; trap - TERM; trap - EXIT; kill -TERM $$'-1 80 trap 'exit 130' INT TERM 85 81 ``` 86 8287 -1 Another option is to convert the relevant signals to explicit `exit` with an88 -1 arbitrary exit code. While it loses out on any default behavior, it is much89 -1 simpler:-1 83 However, this fails to [communicate the signal to the parent -1 84 process](https://www.cons.org/cracauer/sigint.html). The proper solution is to -1 85 remove the handler, call that cleanup code, and then re-raises the signal. To -1 86 avoid executing the cleanup code twice in bash we also need to reset the EXIT -1 87 handler: 90 88 91 89 ```sh 92 90 trap cleanup EXIT93 -1 trap 'exit 130' INT TERM-1 91 trap 'trap - INT TERM EXIT; cleanup; kill -INT $$' INT TERM 94 92 ``` 95 93 96 94 ## Multi-Stage Cleanup @@ -101,29 +99,27 @@ a specific order, and unmount them in reverse order during cleanup. If any of 101 99 these steps fail, we need to unmount only the locations that have already been 102 100 mounted. 103 101104 -1 One approach I have used before is to have a mutable cleanup expression,105 -1 although I don't particularly like it because of the use of `eval`:-1 102 One approach I have used before (but don't particularly like it because of the -1 103 use of `eval`) is to have a mutable cleanup expression: 106 104 107 105 ```sh 108 106 cleanup='true' 109 107 trap 'eval "$cleanup"' EXIT110 -1 trap 'exit 130' INT TERM-1 108 trap 'trap - EXIT INT TERM; eval "$cleanup"; kill -INT $$' INT TERM 111 109 defer() { 112 110 cleanup="$1; $cleanup" 113 111 } 114 112 ``` 115 113116 -1 A flexible option that works consistently across the shells that I have tested117 -1 is using subshells:-1 114 A flexible option that works consistently across the shells that I tested is -1 115 using subshells: 118 116 119 117 ```sh 120 118 echo 'enter script' 121 119 trap "echo 'exit script'" EXIT122 -1 trap 'exit 130' INT TERM123 120 ( 124 121 echo 'enter subshell' 125 122 trap "echo 'exit subshell'" EXIT126 -1 trap 'exit 130' INT TERM127 123 ) 128 124 ``` 129 125 @@ -133,11 +129,10 @@ support traps on the function level, so I didn't look into it any further. 133 129 134 130 ## Conclusion 135 131136 -1 `trap … EXIT` is a very useful tool for doing cleanup in shell scripts.137 -1 Unfortunately, it is no universal “run on everything” hook.-1 132 `trap` is a very useful tool for doing cleanup in shell scripts. Unfortunately, -1 133 it does not provide a universal “run on everything” hook. 138 134139 -1 I do think that cleanup on signals such as SIGINT and SIGTERM is important.140 -1 Bash handles these cases as I would expect, but I do not want to restrict141 -1 myself to a single shell. On the other hand, I also do not want to142 -1 over-complicate things. `trap 'exit 130' INT TERM` seems to provide a good143 -1 balance of these different goals.-1 135 Cleanup on signals such as SIGINT and SIGTERM is important. Bash handles these -1 136 cases as I would expect, but I do not want to restrict myself to a single -1 137 shell. Implementing the correct behavior in a compatible way is much harder -1 138 than it should be.