How to Catch Errors in Bash Scripts on Linux

0
32


fatmawati achmad zaenuri / Shutterstock.com

By default, a Bash script on Linux will report an error but continue to run. We show you how to handle errors yourself so you can decide what should happen next.

Error handling in scripts

Error handling is part of programming. Even if you write flawless code, you can still run into error conditions. Your computer environment changes over time, as you install and uninstall software, create directories, and make upgrades and updates.

For example, a script that used to run without a problem can run into trouble if directory paths change or permissions on a file change. The default action of the Bash shell is to print an error message and continue executing the script. This is a dangerous default value.

If the action that failed is critical to some other processing or action that occurs later in your script, that critical action will not succeed. How disastrous that turns out depends on what the script is trying to do.

A more robust scheme would catch errors and allow the script to work if it needs to shut down or try to remedy the fault condition. For example, if a directory or file is missing, it may be satisfactory for the script to recreate it.

If the script has encountered a problem from which it cannot recover, it can be closed. If the script needs to exit, you may be given the opportunity to do any necessary cleanup, such as deleting temporary files or writing the error condition and exit reason to a log file.

Output state detection

Commands and programs generate a value that is sent to the operating system when they are finished. This is called your exit status. It has a value of zero if there were no errors, or some non-zero value if an error occurred.

We can check the exit status, also known as the return code, of the commands used by the script and determine if the command was successful or not.

In Bash, zero equals true. If the command response is not true, we know that a problem has occurred and we can take appropriate action.

Copy this script into an editor and save it to a file called “bad_command.sh”.

#!/bin/bash

if ( ! bad_command ); then
  echo "bad_command flagged an error."
  exit 1
fi

You will need to make the script executable with the chmod domain. This is a step that is required to make any script executable, so if you want to test the scripts on your own machine, remember to do this for each one. Substitute the appropriate script name in each case.

chmod +x bad_command.sh

Making a script executable using chmod

When we run the script, we see the expected error message.

./bad_command.sh

Checking the exit status of a command to determine if there has been an error

There is no such command as “bad_command”, nor is it the name of a function within the script. It can’t be executed, so the answer is No zero. If the answer is not zero, the exclamation mark is used here as the logical answer. NOT operator — the body of the if the statement is executed.

In a real-world script, this might terminate the script, which our example does, or it might attempt to remedy the fault condition.

could look like the exit 1 the line is redundant. After all, there is nothing else in the script and it will terminate anyway. but using the exit The command allows us to pass an exit status to the shell. If our script is ever called from a second script, that second script will know that this script encountered errors.

you can use logic OR operator with the exit status of a command, and call another command or a function in your script if there is a non-zero response from the first command.

command_1 || command_2

This works because the first command executes OR the second. The leftmost command is executed first. If it is successful, the second command is not executed. But if the first command fails, the second command is executed. So we can structure code like this. This is “logical-o./sh”.

#!/bin/bash

error_handler()
{
  echo "Error: ($?) $1"
  exit 1
}

bad_command || error_handler "bad_command failed, Line: ${LINENO}"

We have defined a function called error_handler . This prints the exit status of the failed command, contained in the variable $? and a line of text that is passed to it when the function is called. This is done in the variable $1. The function ends the script with an exit status of one.

The script tries to run bad_command which obviously fails, so the command to the right of the logic OR operator, ||, is executed. This calls the error_handler function and passes a string that names the command that failed and contains the line number of the command that failed.

We’ll run the script to see the error handler message, and then check the exit status of the script using echo.

./logical-or.sh
echo $?

Using the logical operator OR to call the error handler in a script

our little error_handler The function provides the exit status of the execution attempt. bad_command, the command name, and the line number. This is useful information when you are debugging a script.

The exit status of the script is one. The exit status 127 reported by error_handler means “command not found”. If we wanted, we could use that as the exit status of the script by passing it to exit domain.

Another approach would be to extend error_handler to check the different possible values ​​of the exit status and perform different actions accordingly, using this kind of construct:

exit_code=$?

if [ $exit_code -eq 1 ]; then
  echo "Operation not permitted"

elif [ $exit_code -eq 2 ]; then
  echo "Misuse of shell builtins"
.
.
.
elif [ $status -eq 128 ]; then
  echo "Invalid argument"
fi

Use set to force an exit

If you know that you want your script to exit whenever there is an error, you can force it to do so. it means that you give up the possibility of any cleanup, or indeed any further damage, because your script terminates as soon as it detects an error.

To do this, use the set command with the -e (mistake) option. This tells the script to exit whenever a command fails or returns an exit code greater than zero. Also, using the -E The option ensures that error detection and trapping work in shell functions.

To catch uninitialized variables as well, add the -u (disarmed) option. To ensure that errors are caught in piped streams, add the -o pipefail option. Without this, the exit status of a pipelined script is the exit status of the final command in the sequence. A failed command in the middle of the piped stream would not be detected. the -o pipefail The option should come in the list of options.

The sequence to add to the top of your script is:

set -Eeuo pipefail

Here’s a short script called “unset-var.sh”, with an unset variable in it.

#!/bin/bash

set -Eeou pipefail

echo "$unset_variable"

echo "Do we see this line?"

When we run the script, unset_variable is recognized as an uninitialized variable and the script ends.

./unset-var.sh

Use the set command in a script to terminate the script if an error occurs

The second echo the command is never executed.

Use error trap

The Bash trap command allows you to designate a command or function to be called when a particular signal is generated. Typically this is used to pick up signals like SIGINT which is raised when you press the Ctrl + C key combination. This script is “sigint.sh”.

#!/bin/bash

trap "echo -e 'nTerminated by Ctrl+c'; exit" SIGINT

counter=0

while true
do 
  echo "Loop number:" $((++counter))
  sleep 1
done

the trap The command contains a echo command and the exit domain. It will activate when SIGINT is high. The rest of the script is a simple loop. If you run the script and press Ctrl + C, you will see the message of the trap definition, and the script will terminate.

./sigint.sh

Using trap in a script to trap Ctrl + c

we can use trap with the ERR signal to detect errors as they occur. These can then be fed into a command or function. This is “trap.sh”. We are sending error notifications to a function called error_handler.

#!/bin/bash

trap 'error_handler $? $LINENO' ERR

error_handler() {
  echo "Error: ($1) occurred on $2"
}

main() {
  echo "Inside main() function"
  bad_command
  second
  third
  exit $?
}

second() {
  echo "After call to main()"
  echo "Inside second() function"
}

third() {
  echo "Inside third() function"
}

main

Most of the script is inside the main function, which calls the second Y third functions When an error is encountered, in this case, because bad_command does not exist – the trap statement directs the error to the error_handler function. Passes the failed command’s exit status and line number to the error_handler function.

./trap.sh

Using trap with ERR to detect errors in a script

Our error_handler The function simply lists the details of the error in the terminal window. If you wanted, you could add a exit command to the function so that the script ends. Or you could use a series of if/elif/fi statements to perform different actions for different errors.

It might be possible to fix some errors, others might require the script to be stopped.

One final tip

Catching bugs often means getting ahead of things that can go wrong and putting in code to handle those eventualities should they arise. That’s in addition to making sure the execution flow and internal logic of your script are correct.

If you use this command to run your script, Bash will show you trace output as the script runs:

bash -x your-script.sh

Bash writes the output of the trace to the terminal window. He shows each command with its arguments, if any. This happens after the commands have been expanded, but before they are executed.

It can be of great help in tracking down elusive bugs.

RELATED: How to validate the syntax of a Linux Bash script before running it