TE
TechEcho
Home24h TopNewestBestAskShowJobs
GitHubTwitter
Home

TechEcho

A tech news platform built with Next.js, providing global tech news and discussions.

GitHubTwitter

Home

HomeNewestBestAskShowJobs

Resources

HackerNews APIOriginal HackerNewsNext.js

© 2025 TechEcho. All rights reserved.

“Exit traps” can make your Bash scripts more robust and reliable (2013)

591 pointsby ekiauhcealmost 2 years ago

34 comments

chasilalmost 2 years ago
I used an exit trap to kill an SSH agent that I am running, and I noticed that dash did not kill if the script was interrupted, but only if it ran to successful completion.<p>I asked on the mailing list if this was expected behavior, and it turns out that POSIX only requires EXIT to run on a clean shutdown; to catch interruptions, add more signals.<p><pre><code> trap &#x27;eval $(ssh-agent -k)&#x27; EXIT INT ABRT KILL TERM</code></pre>
评论 #36413887 未加载
评论 #36411786 未加载
评论 #36411051 未加载
评论 #36411377 未加载
评论 #36419000 未加载
评论 #36410784 未加载
评论 #36411333 未加载
arp242almost 2 years ago
An annoying thing about bash is that EXIT will <i>also</i> run on SIGINT (^C), which most other shells won&#x27;t (in my reading it&#x27;s also not POSIX compliant, although the document is a bit vague). Some might argue this is a feature, but IMHO it&#x27;s a bug – sometimes you really <i>don&#x27;t</i> want cleanup to happen so people can inspect the contents of temporary files for debugging. Because trap doesn&#x27;t pass the signal information to the handler it&#x27;s hard to not do cleanup on SIGINT, so it&#x27;s certainly less flexible, and it&#x27;s an annoying incompatibility between bash and any other shell.<p>Also, zsh has a much nicer mechanism for the common case:<p><pre><code> { echo lol } always { # Ensure *all* temporary files are cleaned up. nohup rm -rf &#x2F; &amp; }</code></pre>
评论 #36411501 未加载
评论 #36411205 未加载
评论 #36417091 未加载
评论 #36412111 未加载
评论 #36411183 未加载
评论 #36410461 未加载
abathuralmost 2 years ago
I like combining this with a bash implementation of an event API (<a href="https:&#x2F;&#x2F;github.com&#x2F;bashup&#x2F;events">https:&#x2F;&#x2F;github.com&#x2F;bashup&#x2F;events</a>). This makes it easy&#x2F;idiomatic, for example, to conditionally add cleanup as you go.<p>Glossing over some complexity, but roughly:<p><pre><code> add_cleanup(){ event on cleanup &quot;$@&quot; } trap &quot;event emit &#x27;cleanup&#x27;&quot; HUP EXIT start_postgres(){ add_cleanup stop_postgres # actually start pg } start_apache(){ add_cleanup stop_apache # actually start apache } </code></pre> I wrote a little about some other places where I&#x27;ve used it in <a href="https:&#x2F;&#x2F;www.t-ravis.com&#x2F;post&#x2F;shell&#x2F;neighborly_shell_with_bashup.events&#x2F;#a-quick-look-at-bashupevents" rel="nofollow noreferrer">https:&#x2F;&#x2F;www.t-ravis.com&#x2F;post&#x2F;shell&#x2F;neighborly_shell_with_bas...</a> and <a href="https:&#x2F;&#x2F;t-ravis.com&#x2F;post&#x2F;nix&#x2F;avoid_trap_clobbering_in_nix-shell&#x2F;" rel="nofollow noreferrer">https:&#x2F;&#x2F;t-ravis.com&#x2F;post&#x2F;nix&#x2F;avoid_trap_clobbering_in_nix-sh...</a> (though I make the best use of it in my private bootstrap and backup scripts...)
评论 #36411831 未加载
worikalmost 2 years ago
I have given up the unequal struggle to learn Bash.<p>For me it is &quot;read only&quot;.<p>It is too arcane, even for me.<p>I use Perl now. I tried to reform last year as I was building a system from lots of executable pieces, the perfect job for Bash<p>After much pain and suffering I re-wrote it in Perl. What a (relative) breeze.<p>Just. Don&#x27;t. Do. Bash.<p>Works for me!
评论 #36415788 未加载
throwawaaarrghalmost 2 years ago
Don&#x27;t use traps unless you have to. They are subtly complex and require a great deal more code to deal with edge cases. There is almost always a simpler way to accomplish what you want.<p>If Bash has taught me anything, it&#x27;s that many advanced features should seldom be used. Always resist the temptation to be fancy.
评论 #36414171 未加载
评论 #36412047 未加载
评论 #36415143 未加载
ch33zeralmost 2 years ago
Should go without saying, but don&#x27;t rely on this for anything critical. It&#x27;s not guaranteed this will run, even on successful completion of the script. Simple example: power is cut between the last line of the script and before the trap runs. Just a heads up
评论 #36413502 未加载
评论 #36412098 未加载
filereaperalmost 2 years ago
&gt;The secret sauce is a pseudo-signal provided by bash, called EXIT, that you can trap; commands or functions trapped on it will execute when the script exits for any reason.<p>&quot;Secret Sauce&quot;, why is this secret at all.<p>Nothing against the author who&#x27;s helping the ecosystem here, but is there an authoritative guide on Bash that anyone can recommend?<p>Hopefully something that&#x27;s portable between Mac &amp; Linux.<p>The web is full of contradictory guides and shellcheck seems to be the last line of defense.<p>- <a href="https:&#x2F;&#x2F;github.com&#x2F;koalaman&#x2F;shellcheck">https:&#x2F;&#x2F;github.com&#x2F;koalaman&#x2F;shellcheck</a>
评论 #36412189 未加载
评论 #36412101 未加载
评论 #36419155 未加载
评论 #36413988 未加载
ipnonalmost 2 years ago
I just learned about these through “pair” programming with ChatGPT. It is the quintessential ML-enhanced programming trick: Using some old, robust language feature I’m skilled enough to grok but never had the time to learn about through endless documentation spelunking.<p>My opinion is that LLM pair programming is most or maybe only beneficial to already skilled programmers. ChatGPT can open the door for you, but it can’t show you where the door is. I needed the experience to ask it for a Bash script that handles exit codes gracefully, which is not a question all junior programmers would be able to ask.
ranger207almost 2 years ago
Related, but I use exit traps (or actually ERR traps) to make debugging bash scripts at runtime a little easier. This will print the number of the line of the script that failed along with any error messages from the line that failed. This is useful if for whatever reason your logging system or whatever doesn&#x27;t capture stderr<p>```<p>failure() {<p><pre><code> local lineno=&quot;$1&quot; local msg=&quot;$2&quot; echo &quot;Failed at ${lineno}: ${msg}&quot; </code></pre> }<p>trap &#x27;failure &quot;$LINENO&quot; &quot;BASH_COMMAND&quot;&#x27; ERR<p>```
phhalmost 2 years ago
This 100%.<p>I&#x27;ll complete with patterns I&#x27;m using for exit traps:<p>- for temporary files I have a global array that lists files to remove (and for my use case umount them beforehand)<p>- in the EC2 example, I add a line with just &quot;bash&quot;, so I have an env with the container still running to debug what happened and I just need to close that shell to clear the allocated resources
chrismarlow9almost 2 years ago
Just as an alternative suggestion, consider using a &quot;down file&quot; instead if you can and letting the code gracefully end. Plus you get to write &quot;touch down&quot; and do an end zone dance.
评论 #36414544 未加载
rgraualmost 2 years ago
I couldn&#x27;t find a way to have more than one callback per signal, and created a system to have an array of callbacks:<p><a href="https:&#x2F;&#x2F;github.com&#x2F;kidd&#x2F;scripting-field-guide&#x2F;blob&#x2F;master&#x2F;book.org#array-of-callbacks-on_exit">https:&#x2F;&#x2F;github.com&#x2F;kidd&#x2F;scripting-field-guide&#x2F;blob&#x2F;master&#x2F;bo...</a><p>A nice bonus is that it also keeps the return value of the last non-callback function, so your script behaves better when called from other scripts.
telotortiumalmost 2 years ago
@redsymbol your site has a TLS certificate error. On Chrome I get NET::ERR_CERT_COMMON_NAME_INVALID because your certificate is from mobilewebup.com<p>Otherwise a good article. I use the following code to enable passing the signal name to the trap handler, so that I can kill the Bash process with the correct signal name, which is best practice for Unix signal handling (EXIT would have to be handled specially in `sig_rekill`):<p><pre><code> # Set trap for several signals and pass signal name to trap function. # https:&#x2F;&#x2F;stackoverflow.com&#x2F;a&#x2F;2183063&#x2F;207384 trap_with_arg() { func=&quot;$1&quot; ; shift for sig ; do trap &quot;$func $sig&quot; &quot;$sig&quot; done } sig_rekill() { # Kill whole process group. trap &quot;$1&quot;; kill -&quot;$1&quot; -$$ } # Catch signal and kill whole process group. trap_with_arg sig_rekill HUP INT QUIT PIPE TERM</code></pre>
1letterunixnamealmost 2 years ago
Most people use Bash trap incorrectly, and it should be documented.<p><pre><code> # All normal and error exits trap &#x27;e=$?; trap - EXIT; your cleanup here; exit $e&#x27; EXIT # Error only trap trap &#x27;e=$?; trap - ERR; your error only cleanup here; exit $e&#x27; ERR </code></pre> Save the previous exit condition to preserve it, otherwise it will be destroyed.<p>Untrapping is necessary to prevent multiple calls, especially if it can call exit or fail within the trap handler.<p>You don&#x27;t need an exhaustive list of signals, which is almost never correct in oft touted cargo culted examples.
JohnMakinalmost 2 years ago
I like to use these in combination with set -e and report the error that happened to whatever is capturing stdout for logging.<p>You can report the error code with $? at the start of your trap, IIRC.
waselighisalmost 2 years ago
I wish there was a nicer shell scripting language that simply transpiled to Bash and would generate all this boilerplate code for me. There is <a href="https:&#x2F;&#x2F;batsh.org&#x2F;" rel="nofollow noreferrer">https:&#x2F;&#x2F;batsh.org&#x2F;</a> which has a nice syntax but it doesn&#x27;t even support pipes or redirection, making it pretty worthless for shell scripting. I haven&#x27;t found any other such scripting languages.
评论 #36411353 未加载
DavideNLalmost 2 years ago
Fyi, related article...: &quot;Minimal safe Bash script template&quot; - <a href="https:&#x2F;&#x2F;news.ycombinator.com&#x2F;item?id=25428621">https:&#x2F;&#x2F;news.ycombinator.com&#x2F;item?id=25428621</a>
collinvandyck76almost 2 years ago
I like this but the lazy part of me just treats anything i write into $(mktemp -d) as something that will be eventually GC&#x27;d by the operating system. I have no idea when it actually happens, or if it does at all, but that&#x27;s how i roll.
评论 #36417141 未加载
michaelmioralmost 2 years ago
&gt; and may have security implications too<p>While it&#x27;s certainly true that leaving around files with sensitive data is a security problem, you probably don&#x27;t want to put sensitive data in &#x2F;tmp to begin with.
评论 #36412131 未加载
honkycatalmost 2 years ago
What if instead of using a bunch of features bolted onto a shitty scripting language, we just used a real language like Python?<p>I&#x27;ve read enough hacked-together bash BS to just despise the language.
asylteltinealmost 2 years ago
This is great and I have used it but the better advice is stop using bash scripts for anything other than your own personal scripts and use real languages for anything important.
sigg3almost 2 years ago
Yes. I use them for cleanup in every non-trivial script I write.
smcleodalmost 2 years ago
Yep, I use these all the time, they’re very useful indeed.
gschoalmost 2 years ago
Off topic but I really enjoy the lofi website design!
thangalinalmost 2 years ago
<a href="https:&#x2F;&#x2F;github.com&#x2F;DaveJarvis&#x2F;keenwrite&#x2F;blob&#x2F;main&#x2F;scripts&#x2F;build-template">https:&#x2F;&#x2F;github.com&#x2F;DaveJarvis&#x2F;keenwrite&#x2F;blob&#x2F;main&#x2F;scripts&#x2F;bu...</a><p>My template script provides a way to make user-friendly shell scripts. In a script that uses the template, you define the dependencies and their sources as comma-separated values:<p><pre><code> DEPENDENCIES=( &quot;gradle,https:&#x2F;&#x2F;gradle.org&quot; &quot;warp-packer,https:&#x2F;&#x2F;github.com&#x2F;Reisz&#x2F;warp&#x2F;releases&quot; &quot;tar,https:&#x2F;&#x2F;www.gnu.org&#x2F;software&#x2F;tar&quot; &quot;wine,https:&#x2F;&#x2F;www.winehq.org&quot; &quot;unzip,http:&#x2F;&#x2F;infozip.sourceforge.net&quot; ) </code></pre> You define the command-line arguments:<p><pre><code> ARGUMENTS+=( &quot;a,arch,Target operating system architecture (amd64)&quot; &quot;o,os,Target operating system (linux, windows, mac)&quot; &quot;u,update,Java update version number (${ARG_JAVA_UPDATE})&quot; &quot;v,version,Full Java version (${ARG_JAVA_VERSION})&quot; ) </code></pre> You define the &quot;execute()&quot; method that is called after the arguments are parsed:<p><pre><code> execute() { &#x2F;&#x2F; Make the computer do the work. return 1 } </code></pre> If the script takes arguments, handle each one individually:<p><pre><code> argument() { local consume=2 case &quot;$1&quot; in -a|--arch) ARG_JAVA_ARCH=&quot;$2&quot; ;; -o|--os) ARG_JAVA_OS=&quot;$2&quot; ;; esac return ${consume} } </code></pre> Then call the template&#x27;s main to start the script rolling:<p><pre><code> main &quot;$@&quot; </code></pre> For 99% of the scripts I write, this provides:<p>* Built-in software dependencies verification.<p>* Instructions to the user when requirements are missing.<p>* Simple command-line argument parsing.<p>* Help and logging using ANSI colour.<p>Here&#x27;s a complete script that builds the Windows, Linux, and Mac installers for my Markdown editor:<p><a href="https:&#x2F;&#x2F;github.com&#x2F;DaveJarvis&#x2F;KeenWrite&#x2F;blob&#x2F;main&#x2F;installer.sh">https:&#x2F;&#x2F;github.com&#x2F;DaveJarvis&#x2F;KeenWrite&#x2F;blob&#x2F;main&#x2F;installer....</a><p>There&#x27;s a write-up about creating the script that has a lot more details about how the template works:<p><a href="https:&#x2F;&#x2F;dave.autonoma.ca&#x2F;blog&#x2F;2019&#x2F;05&#x2F;22&#x2F;typesetting-markdown-part-1&#x2F;" rel="nofollow noreferrer">https:&#x2F;&#x2F;dave.autonoma.ca&#x2F;blog&#x2F;2019&#x2F;05&#x2F;22&#x2F;typesetting-markdow...</a><p>Note that it is technically possible to improve the scripts such that handling individual arguments can be done in the template itself. This would require a slightly different argument definition semantics:<p><pre><code> ARGUMENTS+=( &quot;ARG_JAVA_ARCH,a,arch,Target operating system architecture (amd64)&quot; &quot;ARG_JAVA_OS,o,os,Target operating system (linux, windows, mac)&quot; &quot;usage=utile_usage,h,help,Show this help message then exit&quot; ) </code></pre> By detecting an `=` symbol for the first item in the lists, it&#x27;s possible to know whether a command-line argument is assigning a variable value, or whether it means to perform additional functionality. (PR welcome!)
franknord23almost 2 years ago
I wish Bash had &#x27;defer&#x27; like Go.
评论 #36411580 未加载
parenthesesalmost 2 years ago
I&#x27;d like a way to do this for bash functions which I use quite extensively.
LispSporks22almost 2 years ago
Emacs C-c C-t will insert that for ya in shell-script-mode
hawskialmost 2 years ago
It is sad that trap interface is not reversed:<p><pre><code> trap EXIT WHATEVER -- cmd args </code></pre> But as it is common with bash interfaces crystalized before good practices became apparent.
评论 #36416881 未加载
cabalamatalmost 2 years ago
Just use Python or any other proper high-level language that has proper control structures.
评论 #36412268 未加载
tommicaalmost 2 years ago
This is very useful to know - thanks for sharing!
snapcasteralmost 2 years ago
Very cool! didn&#x27;t know about these
xyprotoalmost 2 years ago
Poor man&#x27;s defer
jeronalmost 2 years ago
I thought exit traps were just SPACs