Linux Exit Codes: Evaluating Script Success or Failure

In the Linux operating system, every executed command or script terminates with an integer known as an exit code or return status. Ranging from 0 to 255, these codes serve as a standardized communication mechanism between processes and the operating system. This article explains the vital role exit codes play in signaling whether an operation completed successfully or encountered an error, how the shell captures these signals, and why they are essential for reliable automation.

The Fundamental Rule: Success vs. Failure

The standard convention in Linux assigns specific meaning to numeric exit codes:

For example, standard Linux conventions map code 1 to general errors, 2 to misuse of shell built-ins or syntax errors, 126 to a command that cannot execute due to permissions, and 127 to a "command not found" error. Values above 128 often denote termination caused by fatal OS signals (such as 130 for a script terminated by SIGINT via Ctrl+C).

Capturing Exit Codes with $?

The Linux shell tracks the exit code of the most recently executed foreground command in the special parameter $?. Inspecting this variable immediately after a command runs provides instant insight into its execution status:

ls /existing-directory
echo $?  # Outputs 0

ls /non-existent-directory
echo $?  # Outputs 2

Because $? is overwritten immediately by the next command, scripts must capture or evaluate it right away.

Core Purposes of Exit Codes

1. Conditional Logic and Flow Control

Exit codes enable automated decision-making in scripts. Shell control structures such as if, while, and logical operators evaluate whether a command returns a zero or non-zero status to determine execution paths:

2. Process Orchestration and CI/CD Pipelines

Automated environments—such as cron jobs, configuration management tools (Ansible, Puppet), and CI/CD pipelines (GitHub Actions, GitLab CI, Jenkins)—rely exclusively on exit codes to monitor build and deployment steps. If a script returns a non-zero code, the pipeline flags the step as failed, halts subsequent dependent jobs, and triggers alerts.

3. Granular Error Diagnostics

Scripts can define custom exit codes using the exit command (e.g., exit 3 for missing input files, exit 4 for database connection timeouts). This practice allows administrators and parent scripts to diagnose failures programmatically without manually parsing log output.

4. Safe Script Termination

By using shell options such as set -e (errexit), a script can instruct the shell to abort immediately if any pipeline returns a non-zero status. This prevents cascading errors from causing unintended side effects, such as running destructive cleanup routines after a failed build.