Traps in Bash


There are already many articles written about Bash scripts, by people much more proficient at Bash than I am. This article is primarily serves as a reference for myself, but I hope it can also be helpful to others.

If there is anything that you think is missing or that you want to discuss, let me know.

Exit code capture

Understanding how Bash handles exit codes can be a bit difficult to understand fully, so I’ll try to explain this as simple as possible.

Let’s look at a snippet of code I use in my scripts to handle cleanup of that script on exit.

trap '[[ "$?" -eq 1 ]] || [[ "$?" -gt 128 ]] && clean' INT QUIT TERM EXIT

This code captures the exit status of the script and runs some code, we’ll get back to this.

To understand exit statuses however, we need to take a look at signals first.

Signals

Signals in Linux are used to communicate with and control processes and are issued by the kernel, other processes or users.

An example of a couple of signals to get the picture:

  • SIGHUP (signal 1)
    • Description: Hangup detected or death of controlling process.
    • Use Case: Used to reload configuration files by the process.
  • SIGINT (signal 2)
    • Description: Interrupt from keyboard (CTRL+C).
    • Use Case: Terminate a process by the user.
  • SIGKILL (signal 9)
    • Description: Kill signal (also known as $ kill -9)
    • Use Case: Kill it with fire.

There are many more signals used, but the main thing to understand is that a process can be manipulated in different ways and each of which have their own signal that is passed to the process. The process then acts on (or is being made to act upon) this signal and reports an exit status of 128 plus the signal number. If left untouched, this will be the exit status of the command or script that was run. We can influence this though, more on that below.

Exit statuses

Bash exit statuses are numerical values that are returned by the process when it terminates. They can range from 0 to 255 and can be divided in two main groups. A few examples:

Normal exit statuses (0-128):

  • 0: Successful completion
  • 1: General error
  • 2-127: Non-reserved status codes (customizable)

Signal-induced exit statuses(128-255):

  • 128: Not used
  • 129 (128 + signal 1): SIGHUP
  • 130 (128 + signal 2): SIGINT (CTRL+C)
  • 137 (128 + signal 9): SIGKILL

Run $ kill -l to see the full list.

It’s a trap

Remember when I said that the exit status code of the process will be 128 + <signal_number>, but that this can be influenced?

Enter the trap command.
This is the syntax for the trap command: trap [commands] [signals]

This command initiates at the beginning of your script and continuously listens for any of the signals your script receives. If any of the predefined signals are detected, the trap is triggered and it will run a predefined command.

[signals] are the signals the trap command should intercept. This can be a numerical signal, but may as well be signal names like SIGKILL.
[command] is the command that trap should execute when it has intercepted one of the signals defined in [signals].

For example, the next snippet will reboot (assuming it has the permissions) your machine, when it receives one of the signals in INT QUIT:

trap 'reboot' INT QUIT

Now, if we go back to the above snippet:

trap '[[ "$?" -eq 1 ]] || [[ "$?" -gt 128 ]] && clean' INT QUIT TERM EXIT

We see that the trap will trigger on one of the signals in INT QUIT TERM EXIT. If this happens, trap will run the following script:

[[ "$?" -eq 1 ]] || [[ "$?" -gt 128 ]] && clean

This means as much as “If exit signal is 1 or greater than 128, run ‘clean’”. Clean here is a function in the script that will clean up temporary files so the script will run properly next time.

I hope this helped you or at least cleared some things up and will help you write better Bash scripts.

See you next time!


Signals, exit codes and traps in Bash
2024-12-17
Table Of Contents: