Exit Codes and Error Handling

ShellBeginner
Practice Now

Introduction

Imagine if every time you asked someone to do a job, they gave you a small card afterwards.

  • A Green Card (0) means "I did it perfectly."
  • A Red Card (1-255) means "Something went wrong."

In Linux, every command does exactly this. It is called an exit code or exit status.

  • 0: Success.
  • Not 0: Failure.

Most beginners ignore these codes because they are usually invisible unless you ask for them. Professional scripters pay close attention to them because exit status is one of the shell's main ways of reporting success or failure between commands and scripts.

This is a core Unix design pattern: small programs communicate with each other using simple conventions. Text can travel through standard output, and success or failure travels through the exit status. That is why exit codes matter so much in automation.

In this lab, you will learn to read these secret cards using the variable $? and stop your scripts before they make a mess.

Check Command Status with $?

How do you see this invisible "Scorecard"? You look at a special variable called $? directly after running a command.

Let's look at a success first.

Open your terminal and list your files:

ls -l

It worked, right? Now, let's ask "What was the score?"

echo $?

You should see 0. In Linux logic, 0 is the number of errors. Zero errors means perfect success.

Now, let's force a failure. Try to list a file that doesn't exist:

ls ghosts.txt

Linux complains it can't find it. Now ask for the score again:

echo $?

You will see 2. It's not 0, so it's a failure.

The exact non-zero number can vary by command and situation, but for now the key rule is simple: 0 means success, and anything else means failure. Later, as you work with scripts and tools, you will see that some programs use different non-zero codes to describe different kinds of problems.

Crucial Rule: $? is short-term memory. It only remembers the very last thing you did. If you run ls (fail), then run echo "hello", then check $?, you will see 0 because the echo succeeded! You must check it immediately.

Create a Failing Script

Here is the danger zone. By default, a script ignores red cards. It just keeps going.

Imagine a script that says:

  1. Make a backup.
  2. Delete the original files.

If step 1 fails (backup full), step 2 still runs, and you lose everything.

Let's simulate this "Blind Script".

Create copy_script.sh:

nano copy_script.sh

Type this code. It tries to copy a phantom file, then claims success.

#!/bin/bash

## Attempt to copy a ghost
cp ghost_file.txt destination.txt

## Print a happy message
echo "Script finished execution"

Save and Exit. Make executable:

chmod +x copy_script.sh

Run it:

./copy_script.sh

You see the error message from cp, but then you see "Script finished execution". The script crashed, but it kept walking!

Check the score of the script itself:

echo $?

It returns 0. Why? Because the last command (echo) succeeded. This script is a liar. It failed its main job but reported success to the system.

This is an important lesson: a shell script also has an exit status, and if you do nothing special, it often becomes the exit status of the last command that ran. That is why a script can accidentally hide a failure that happened earlier.

Implement Error Handling

We need to teach our script to stop at a Red Light.

We generally use the structure: "Attempt command X. If Exit Code is NOT 0, then Panic."

The syntax [ $? -ne 0 ] uses the test command, written here in bracket form. -ne means "not equal" for numbers. So the shell is asking: "Is the previous command's exit status not equal to zero?"

Edit your script copy_script.sh:

nano copy_script.sh

Change it to:

#!/bin/bash

cp ghost_file.txt destination.txt

## Check if the score is NOT equal (-ne) to 0
if [ $? -ne 0 ]; then
  echo "Error: The copy operation failed!"
  exit 1
fi

echo "Script finished execution"

Decoding:

  • if [ $? -ne 0 ]: The Guard checks the card.
  • exit 1: The Red Button. This kills the script immediately and sets the global score to 1 (Error).

The exit command ends the current shell script and returns that number to whatever launched it, such as your terminal, another script, or an automation system. In other words, you are not just stopping execution locally; you are reporting status back to the outside world.

Save and Exit.

Run it again:

./copy_script.sh

The script prints the error and dies. It does not print "Script finished execution".

Check the score:

echo $?

It is 1. Now the script is honest about its failure.

Summary

You have learned how to listen to the secret language of commands.

In this lab, you practiced:

  1. Reading Scorecards: Using $? to see if a command won (0) or lost (1-255).
  2. Exposing Liars: Seeing how scripts can hide errors if you aren't careful.
  3. Referees: Using exit 1 to stop the game when a foul happens.

This "Check and Exit" pattern is the difference between a flimsy script and a professional one.