We have all been there, you write a bash script that creates some temporary files and/or directories for processing some information. You complete your script with some basic testing and all is well. You set your cron job to run the script every day at noon. Six days later you realize that the script has been exiting prematurely and leaving a bunch of trash files in the file system. Or worse, potentially sensitive data is left unprotected. Enter the trap command.
The above scenario is a bit dramatic, but not completely ridiculous. If you are automating tasks with bash scripts you will eventually run into a premature exit. Either as the result of an error, a change in the environment, or an unanticipated user action. Creating scripts that deal with this scenario is imperative to keeping a clean and secure system.
What is Trap?
Trap is a bash builtin command that is used to respond to process signals. A signal is a notification sent to a process to notify it of an event.
The most common signal people are familiar with is SIGINT (Signal Interrupt) which is sent when you press CTRL+C to interrupt a process on the command line.
There are a lot of signals available to the operating system. For this tutorial we will focus on EXIT. The exit signal is a bash builtin and can be used to catch any signal. For all intents and purposes, if you trap exit, it will be executed when the shell process terminates.
Exit the shell, returning a status of n to the shell’s parent. If n is omitted, the exit status is that of the last command executed. Any trap on
-Bash ManualEXIT
is executed before the shell terminates.
Display a List of Signal Names and Their Numbers
You can display a list of signals, and their associated numbers, by using the trap command list option ( -l ).
The Wrong Way to Clean Up on Exit
If you have been around long enough you have seen scripts that use the rm command at the end to clean up temporary files.
#!/bin/bash
yum -y update > /tmp/output.txt
if grep -qi "kernel" /tmp/output.txt; then
mail -s "KERNEL UPDATED" [email protected] < /tmp/output.txt
fi
rm -f /tmp/output.txt
This is usually a sign of someone trying to clean up after themselves. Cleaning up is a good thing, just not executed correctly in this case. If anything before the rm command exits for any reason the output.txt file will not be deleted. This could get out of hand if the script uses mktemp or a dated filename. It may also be a security concern is sensitive data is in the temporary file. Additionally, if you write a long script that has several exit points, you will have to keep track of each exit and ensure you cleanup.
Using Trap on EXIT
The basic syntax for trap is:
trap [action] [signal]
or written as the man page calls it:
trap [-lp] [[arg] sigspec ...]
Basic Trap for File Cleanup
Using an trap to cleanup is simple enough. Here is an example of using trap to clean up a temporary file on exit of the script.
#!/bin/bash
trap "rm -f /tmp/output.txt" EXIT
yum -y update > /tmp/output.txt
if grep -qi "kernel" /tmp/output.txt; then
mail -s "KERNEL UPDATED" [email protected] < /tmp/output.txt
fi
NOTE: It is important that the trap statement be placed at the beginning of the script to function properly. Any commands above the trap can exit and not be caught in the trap.
Now if the script exits for any reason, it will still run the rm command to delete the file. Here is an example of me sending SIGINT (CTRL+C) to interrupt the script while it is running.
# ./test.sh
^Cremoved ‘/tmp/output.txt’
If we did not use trap here, the output.txt file would be left on the system.
NOTE: I added verbose ( -v ) output to the rm command so it prints "removed". The ^C signifies where I hit CTRL+C to send SIGINT.
This is a much cleaner and safer way to ensure the cleanup occurs when the script exists. Using EXIT ( 0 ) instead of a single defined signal (i.e. SIGINT - 2) ensures the cleanup happens on any exit, even successful completion of the script.
Using Traps to Ensure Service Availability
You can use traps for more than file cleanup. Imagine a scenario where you created a script to stop a service to do some automated task. If one of the commands exits during the script, then the service would remained stopped. You can use the trap on EXIT to ensure the service comes back up and is available.
Here we will stop the smb (SAMBA) service to create a tar archive of our share. We will use trap to ensure the service starts, even if the archive procedure exits or the script is otherwise interrupted.
#!/bin/bash
# Set trap on EXIT for service
trap "systemctl start smb.service" EXIT
# Stop service
systemctl stop smb.service
# Create the tarball
tar czf windows_share.tar.gz /srv/WindowsTeam/
# There is no need to restart the service in the script,
# the trap will catch any exit, even successful
This may not be the most glamorous example. But I think it delivers the point.
Ensure a Port is Closed After Script Completion
If you run a script that needs a port opened on the firewall, you don't want that script to exit and leave the port open. Use the trap command.
Here is an example of me opening a port to let my kids watch movies on my plex server for 2 hours. After two hours the ports close up for their roku device and it's time to read (I am a cord cutter - cable TV rots the brain).
#!/bin/bash trap "iptables -D INPUT -p tcp -s 10.0.0.222 --dport 32400 -j ACCEPT" EXIT echo "Opening ports for kiddos 2 hours of TV time" iptables -I INPUT -p tcp -s 10.0.0.222 --dport 32400 -j ACCEPT sleep 7200
This is just a small example not my actual script, the real on is more involved than this with a countdown timer so I know when to enforce the reading rule. Leave a comment below if the real script is of interest to you.
Using a Function in a Trap Call
If you want to run multiple commands in a trap it may be cleaner to put those commands into a function, then simply call the function in the trap statement. We will use our previous examples for illustration.
function egress {
rm -f /tmp/output.txt
systemctl start smb.service
iptables -D INPUT -p tcp -s 10.0.0.222 --dport 32400 -j ACCEPT
}
# Call the egress function
trap egress EXIT
# Script contents
# Do some stuff
# Then some more stuff
# Do stuff to the stuff already done
# etc...
# When the script is completed or exits for any reason, the
# commands in the egress function will be executed.
Here we have multiple actions in the egress function. We then call that function in the trap statement. When the script exits, the egress function will run.
Conclusion
At the beginning of my Linux experience I spent a lot of time dealing with the fallout of premature script exits. The trap command is a simple and effective way to ensure your bash scripts exit cleanly.
In this tutorial we listed all the signals you can trap and there are more bash builtins that we did not even mention. I will save those for a future tutorial (DEBUG anybody?).
We would love to hear the ways you use traps, please comment below if you have some to share!
Resources
Leave a Reply Cancel reply
This site uses Akismet to reduce spam. Learn how your comment data is processed.
13 Comments
Join Our Newsletter
Categories
- Bash Scripting (17)
- Basic Commands (50)
- Featured (7)
- Just for Fun (5)
- Linux Quick Tips (98)
- Linux Tutorials (65)
- Miscellaneous (15)
- Network Tools (6)
- Reviews (2)
- Security (32)
Good info. A small observation, I think is should be reject instead of accept for port closing in trap command, as:
trap "iptables -D INPUT -p tcp -s 10.0.0.222 --dport 32400 -j REJECT" EXIT
No, I am using INPUT -"D" to delete the rule. You can delete a rule in iptables by calling it just as you would when adding it but using -D instead of -A.
For example, let's say you added a rule like so:
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
You can then delete it using the same syntax, but using -D instead of -A (or -I)
iptables -D INPUT -p tcp --dport 22 -j ACCEPT <<< This would delete the rule We added a rule for the script, then removed the rule when it exited. If you configured iptables correctly there should be a policy or catch all denying traffic unless explicitly allowed. Read my tutorial on the Basics of IPtables Make sense?
this is good information on top of the article.
agreed I never knew I could delete a rule like that in iptables
Great post! I will most definitely use this! Thanks for the info.
Wow, I should really look into this more. I guess I like Traps now!
Very informative thanks. This is a new concept to me so I need to go test it out.
Useful post.
Thank you for sharing 🙂
Thanks for detailed explanation.
I am a shell scripting beginner and this is very interesting and useful. thanks
Thanks Steve. Useful tips, explained very clearly.
{ Can't see the verbose/-v in the remove example??? }
Very well articulated! Many thanks for putting this together.
Thanks for this post, very useful info!