Skip to main content

How to Exit Script with Error Code in Bash

• 4 min read
bash error handling exit codes debugging

Quick Answer: How to Exit with an Error Code

Use exit with a number: exit 0 for success, exit 1 (or any non-zero) for failure. Check the exit code of the last command with $?, or use if command; then to check directly in conditionals.

Quick Comparison: Error Handling Methods

MethodUse CaseClarity
$?Check most recent exit codeVery clear
if command;Chain logic based on successExcellent
&& and ||Inline chainingConcise
Custom codesDistinguish error typesBest for automation

Bottom line: Use exit 0 for success, exit 1 for general errors, and exit 2+ for specific error types in automation scripts.


Understanding Exit Codes

Every command you run in Bash has an exit code—a number that tells you whether the command succeeded or failed. Exit codes are the fundamental way Bash scripts communicate results to their callers. They’re essential for writing reliable automation because they let you detect problems and respond appropriately.

Here’s the convention:

  • 0 means success (the command did what you asked)
  • Any non-zero value (1-255) means failure
  • Different error codes can indicate different types of failures

This might feel backwards if you’re from other languages (where 0 is often false), but it’s consistent with Unix philosophy. Success returns 0 because it’s used as a truth value in conditionals, and in Unix, 0 is true.

Checking the Exit Code of the Last Command

After any command runs, Bash stores its exit code in a special variable called $?. You can check this immediately to see if the command succeeded or failed.

$ ls /home
user1 user2
$ echo $?
0

$ ls /nonexistent
ls: cannot access '/nonexistent': No such file or directory
$ echo $?
2

The ls command that succeeded returned 0. The one that failed returned 2 (a “no such file or directory” error). Each command has its own exit code—the successful ls returned 0, the failed one returned 2.

Important gotcha: The value of $? only shows the exit code of the most recent command. Once you run another command, $? changes to that command’s exit code:

$ ls /nonexistent  # This fails
ls: cannot access '/nonexistent': No such file or directory
$ echo "Checking..."  # This succeeds
Checking...
$ echo $?
0  # This shows the exit code of echo, not ls!

This is a common mistake—you check a command’s result, run another command (like echo), then check $? and get the wrong result. The solution is to save the exit code immediately if you need it later:

$ ls /nonexistent
ls: cannot access '/nonexistent': No such file or directory
$ error_code=$?
$ echo "The error was: $error_code"
The error was: 2

When to Use $? Exit Codes

Use $? when:

  • You need to check if a command succeeded or failed
  • You want to know the specific error code
  • You’re debugging and need to understand why something failed

Using Exit Codes in Scripts

The most common use of exit codes is controlling script flow with if statements:

#!/bin/bash

# Check if a file exists
if [ -f "$1" ]; then
  echo "File exists: $1"
else
  echo "File does not exist: $1"
  exit 1  # Exit with error code 1
fi

# Check if a command succeeds
if grep -q "password" /etc/shadow; then
  echo "Shadow file contains password entries"
else
  echo "Failed to read shadow file"
  exit 2  # Exit with error code 2
fi

echo "Script completed successfully"
exit 0  # Exit with success code

Using the Exit Code of a Command Directly

You can use a command directly in an if statement - the if statement checks the exit code:

#!/bin/bash

# Using grep with exit code
if grep -q "admin" /etc/passwd; then
  echo "Admin user found"
else
  echo "Admin user not found"
fi

# Using a test command
if [ -d "/home" ]; then
  echo "Home directory exists"
else
  echo "Home directory not found"
  exit 1
fi

# Using ping to check connectivity
if ping -c 1 google.com > /dev/null 2>&1; then
  echo "Internet connection is available"
else
  echo "No internet connection"
  exit 1
fi

Practical Example: Backup Script with Error Handling

Here’s a real-world backup script that uses exit codes properly:

#!/bin/bash

BACKUP_DIR="/backups"
SOURCE_DIR="/home/user/important_files"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/backup_$TIMESTAMP.tar.gz"

# Check if source directory exists
if [ ! -d "$SOURCE_DIR" ]; then
  echo "ERROR: Source directory does not exist: $SOURCE_DIR"
  exit 1
fi

# Check if backup directory exists
if [ ! -d "$BACKUP_DIR" ]; then
  echo "ERROR: Backup directory does not exist: $BACKUP_DIR"
  exit 1
fi

# Create the backup
echo "Creating backup: $BACKUP_FILE"
tar -czf "$BACKUP_FILE" "$SOURCE_DIR"

# Check if backup was successful
if [ $? -eq 0 ]; then
  echo "Backup completed successfully"
  echo "File size: $(du -h "$BACKUP_FILE" | cut -f1)"
  exit 0
else
  echo "ERROR: Backup failed!"
  exit 1
fi

When this script runs:

  • It verifies the source and backup directories exist (exiting with code 1 if not)
  • It creates the backup
  • It checks if tar succeeded (exit code 0)
  • It exits with appropriate codes so the calling process knows what happened

Chaining Commands with && and ||

You can chain commands using && (run if previous succeeded) and || (run if previous failed):

# && operator: only run if previous command succeeded
$ mkdir /tmp/test && cd /tmp/test && echo "Directory created and entered"
Directory created and entered

# || operator: only run if previous command failed
$ ls /nonexistent || echo "Directory not found, continuing..."
ls: cannot access '/nonexistent': No such file or directory
Directory not found, continuing...

# Combining both
$ mkdir /tmp/newdir && cp file.txt /tmp/newdir || echo "Copy failed"

This is much cleaner than writing if-else statements for every command.

Custom Exit Codes in Your Scripts

You can define your own exit codes to indicate different types of errors:

#!/bin/bash

# Define exit codes at the top for clarity
readonly E_SUCCESS=0
readonly E_MISSING_FILE=1
readonly E_INVALID_FORMAT=2
readonly E_WRITE_FAILED=3

if [ ! -f "$1" ]; then
  echo "ERROR: File not found: $1"
  exit $E_MISSING_FILE
fi

if ! grep -q "^valid_header" "$1"; then
  echo "ERROR: File does not have valid header"
  exit $E_INVALID_FORMAT
fi

if ! cp "$1" "$1.backup"; then
  echo "ERROR: Failed to create backup"
  exit $E_WRITE_FAILED
fi

echo "File processed successfully"
exit $E_SUCCESS

Now callers can check the specific error code to understand what went wrong:

$ ./process.sh myfile.txt
File processed successfully

$ echo $?
0

Printing Exit Code on Test Failures

When debugging, it’s helpful to see the actual exit code:

#!/bin/bash

# Test a command and show its exit code
test_command() {
  local cmd="$1"
  echo "Running: $cmd"
  eval "$cmd"
  local exit_code=$?
  echo "Exit code: $exit_code"
  return $exit_code
}

test_command "ls /home"
test_command "ls /nonexistent"
test_command "grep root /etc/passwd"

Output:

Running: ls /home
user1 user2
Exit code: 0
Running: ls /nonexistent
ls: cannot access '/nonexistent': No such file or directory
Exit code: 2
Running: grep root /etc/passwd
root:x:0:0:root:/root:/bin/bash
Exit code: 0

Key Points to Remember

  • Exit code 0 means success, any non-zero means failure
  • Check the exit code with $? immediately after a command
  • Use if command; then to check exit codes in conditionals
  • Use && and || to chain commands based on exit codes
  • Define your own exit codes for better error handling
  • Exit with appropriate codes so parent processes know what happened
  • Don’t assume a command succeeded - always check the exit code

Common Exit Codes

CodeMeaning
0Success
1General errors
2Misuse of shell command
126Command cannot execute
127Command not found
128Invalid exit argument
255Exit status out of range

Summary

Exit codes are how Bash scripts communicate success or failure. By using them properly, you can write scripts that handle errors gracefully, chain commands logically, and provide useful feedback to whoever runs your script. Always check exit codes and use them to control your script’s flow.