Exit Codes
POSIX exit codes and common conventions.
Standard Unix Exit Codes
Basic Codes (POSIX):
| Code | Meaning |
|---|---|
|
Success / True |
|
General error / False |
|
Misuse of shell command (syntax error, invalid option) |
Reserved Exit Codes (Bash):
| Code | Meaning |
|---|---|
|
Command invoked but not executable (permission denied) |
|
Command not found |
|
Invalid exit argument (exit takes only integers 0-255) |
|
Fatal error, killed by signal |
|
Script terminated by Ctrl+C (128 + 2 = SIGINT) |
|
Killed by SIGKILL (128 + 9) |
|
Segmentation fault (128 + 11 = SIGSEGV) |
|
Broken pipe (128 + 13 = SIGPIPE) |
|
Terminated by SIGTERM (128 + 15) |
|
Exit status out of range (code modulo 256) |
BSD sysexits.h Standard:
| Code | Meaning |
|---|---|
|
EX_USAGE - Command line usage error |
|
EX_DATAERR - Data format error |
|
EX_NOINPUT - Cannot open input |
|
EX_NOUSER - Addressee unknown |
|
EX_NOHOST - Host name unknown |
|
EX_UNAVAILABLE - Service unavailable |
|
EX_SOFTWARE - Internal software error |
|
EX_OSERR - System error (e.g., can’t fork) |
|
EX_OSFILE - Critical OS file missing |
|
EX_CANTCREAT - Can’t create output file |
|
EX_IOERR - Input/output error |
|
EX_TEMPFAIL - Temporary failure; retry |
|
EX_PROTOCOL - Remote protocol error |
|
EX_NOPERM - Permission denied |
|
EX_CONFIG - Configuration error |
Unix Signal Numbers
Common Signals (used in 128+n codes):
| Signal | Number | Description |
|---|---|---|
|
1 |
Hangup (terminal closed) |
|
2 |
Interrupt (Ctrl+C) |
|
3 |
Quit (Ctrl+\, core dump) |
|
4 |
Illegal instruction |
|
5 |
Trace/breakpoint trap |
|
6 |
Abort signal (assert failed) |
|
7 |
Bus error |
|
8 |
Floating point exception |
|
9 |
Kill (cannot be caught) |
|
10 |
User-defined signal 1 |
|
11 |
Segmentation fault |
|
12 |
User-defined signal 2 |
|
13 |
Broken pipe |
|
14 |
Alarm clock (timeout) |
|
15 |
Termination (graceful) |
|
17 |
Child status changed |
|
18 |
Continue if stopped |
|
19 |
Stop (cannot be caught) |
|
20 |
Terminal stop (Ctrl+Z) |
Calculate exit code from signal:
# Process killed by signal -> exit code = 128 + signal_number
# Ctrl+C (SIGINT=2): 128 + 2 = 130
# kill -9 (SIGKILL=9): 128 + 9 = 137
# SIGSEGV (11): 128 + 11 = 139
# Decode exit code
exit_code=137
if (( exit_code > 128 )); then
signal=$((exit_code - 128))
echo "Killed by signal $signal ($(kill -l $signal))"
fi
Send signals:
kill -SIGTERM $PID # Graceful termination (default)
kill -9 $PID # Force kill (SIGKILL)
kill -SIGSTOP $PID # Pause process
kill -SIGCONT $PID # Resume process
kill -SIGHUP $PID # Reload config (convention)
kill -SIGUSR1 $PID # Custom signal 1
Exit Code Handling in Scripts
Check last command:
# $? contains exit code of last command
some_command
if [[ $? -eq 0 ]]; then
echo "Success"
else
echo "Failed with code $?"
fi
# More idiomatic - use command directly
if some_command; then
echo "Success"
else
echo "Failed"
fi
# Capture exit code for later use
some_command
status=$?
# ... other commands ...
if [[ $status -ne 0 ]]; then
echo "Earlier command failed"
fi
Handle specific codes:
some_command
case $? in
0) echo "Success" ;;
1) echo "General error" ;;
2) echo "Invalid arguments" ;;
126) echo "Permission denied" ;;
127) echo "Command not found" ;;
*) echo "Unknown error: $?" ;;
esac
Exit with appropriate code:
#!/bin/bash
# Exit on first error
set -e
# Exit with usage error
usage() {
echo "Usage: $0 <arg>" >&2
exit 64 # EX_USAGE
}
# Validate input
[[ -z "$1" ]] && usage
# Check file exists
if [[ ! -f "$1" ]]; then
echo "Error: File not found: $1" >&2
exit 66 # EX_NOINPUT
fi
# Check permissions
if [[ ! -r "$1" ]]; then
echo "Error: Cannot read: $1" >&2
exit 77 # EX_NOPERM
fi
# Success
exit 0
Pipeline exit codes:
# ${PIPESTATUS[@]} contains exit codes of all pipeline commands
cat nonexistent | grep foo | wc -l
echo "Exit codes: ${PIPESTATUS[0]} ${PIPESTATUS[1]} ${PIPESTATUS[2]}"
# Output: Exit codes: 1 1 0
# Exit on any pipeline failure (bash)
set -o pipefail
cat nonexistent | grep foo | wc -l # Now exits with non-zero
# Check if any pipeline command failed
process_data | transform | output
if [[ "${PIPESTATUS[*]}" =~ [^0\ ] ]]; then
echo "Pipeline had failures"
fi
Subshell exit codes:
# Subshell exit code becomes $?
(
cd /nonexistent
echo "This won't run"
)
echo "Subshell exit: $?" # 1
# Command substitution doesn't set $? (use pipefail)
result=$(failing_command) # $? is set but easy to miss
# Better: check explicitly
if ! result=$(failing_command); then
echo "Command failed"
fi
curl Exit Codes
| Code | Meaning |
|---|---|
|
Success |
|
Unsupported protocol |
|
Failed to initialize |
|
URL malformed |
|
Couldn’t resolve proxy |
|
Couldn’t resolve host |
|
Failed to connect to host |
|
HTTP page not retrieved (4xx/5xx with -f flag) |
|
Write error |
|
Read error |
|
Operation timed out |
|
SSL connect error |
|
Too many redirects |
|
Peer’s SSL certificate wasn’t OK |
|
Server didn’t reply |
|
Failed sending network data |
|
Failure receiving network data |
|
SSL certificate problem (verification failed) |
|
Login denied |
Usage:
# Use -f to fail on HTTP errors (returns 22)
curl -sf "https://api.example.com/endpoint" || echo "API call failed"
# Get HTTP code without failing
http_code=$(curl -s -o /dev/null -w "%{http_code}" "$url")
# Combine exit code and HTTP code
response=$(curl -sf -w "\n%{http_code}" "$url")
http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | sed '$d')
SSH/SCP Exit Codes
| Code | Meaning |
|---|---|
|
Success |
|
Generic error |
|
Connection failed |
|
Host key verification failed |
|
Read error (local file) |
|
Write error (local file) |
|
Network error |
|
Protocol error |
|
Authentication failed |
|
Memory allocation error |
|
File error (local) |
|
Ctrl+C (user interrupt) |
|
Unknown host |
|
Connection refused |
|
Connection closed |
|
Connection lost |
|
Permission denied (server) |
|
No such file (remote) |
|
SSH error (general, often auth fail) |
Usage:
# Check SSH connection
ssh -o ConnectTimeout=5 -o BatchMode=yes user@host "exit 0"
case $? in
0) echo "Connection successful" ;;
255) echo "SSH error (auth, connection, or config)" ;;
*) echo "Exit code: $?" ;;
esac
# SCP with error handling
scp file.txt user@host:/path/ || {
echo "SCP failed with code $?"
exit 1
}
rsync Exit Codes
| Code | Meaning |
|---|---|
|
Success |
|
Syntax or usage error |
|
Protocol incompatibility |
|
Errors selecting input/output files/dirs |
|
Requested action not supported |
|
Error starting client-server protocol |
|
Daemon unable to append to log file |
|
Error in socket I/O |
|
Error in file I/O |
|
Error in rsync protocol data stream |
|
Errors with program diagnostics |
|
Error in IPC code |
|
Received SIGUSR1 or SIGINT |
|
Waitpid() error |
|
Memory allocation error |
|
Partial transfer (some files not transferred) |
|
Partial transfer (source file vanished) |
|
Limit reached (--max-delete) |
|
Timeout in data send/receive |
|
Timeout waiting for daemon connection |
Usage:
# Handle partial transfer
rsync -avz source/ dest/
exit_code=$?
if [[ $exit_code -eq 0 ]]; then
echo "Complete success"
elif [[ $exit_code -eq 23 || $exit_code -eq 24 ]]; then
echo "Partial transfer (some files missing/vanished)"
else
echo "rsync failed: $exit_code"
fi
Git Exit Codes
| Code | Meaning |
|---|---|
|
Success |
|
Generic error / false result (e.g., diff found changes) |
|
Fatal error (usually with error message) |
|
Invalid options |
Common scenarios:
# git diff returns 1 if differences found
git diff --quiet HEAD
if [[ $? -eq 0 ]]; then
echo "No changes"
else
echo "Working tree has changes"
fi
# Check if branch exists
git rev-parse --verify --quiet branch-name
[[ $? -eq 0 ]] && echo "Branch exists"
# Check if repo is clean
if git diff-index --quiet HEAD --; then
echo "Clean"
else
echo "Dirty"
fi
# Check if inside git repo
git rev-parse --is-inside-work-tree &>/dev/null
[[ $? -eq 0 ]] && echo "In git repo"
grep Exit Codes
| Code | Meaning |
|---|---|
|
Match found (one or more lines) |
|
No match found |
|
Error (file not found, permission, syntax) |
Usage:
# Check for pattern without output
if grep -q "pattern" file.txt; then
echo "Found"
fi
# Differentiate no match from error
grep "pattern" file.txt
case $? in
0) echo "Found matches" ;;
1) echo "No matches" ;;
2) echo "Error occurred" ;;
esac
# Suppress errors, only check for matches
if grep -sq "pattern" file.txt 2>/dev/null; then
echo "Found (ignoring errors)"
fi
Test/[ ] Exit Codes
| Code | Meaning |
|---|---|
|
True (condition met) |
|
False (condition not met) |
|
Error (syntax, invalid args) |
Common tests:
# File tests
[[ -e file ]] # Exists
[[ -f file ]] # Regular file
[[ -d dir ]] # Directory
[[ -L link ]] # Symbolic link
[[ -r file ]] # Readable
[[ -w file ]] # Writable
[[ -x file ]] # Executable
[[ -s file ]] # Size > 0
[[ -n "$var" ]] # String not empty
[[ -z "$var" ]] # String is empty
# Comparisons
[[ $a -eq $b ]] # Numeric equal
[[ $a -ne $b ]] # Numeric not equal
[[ $a -lt $b ]] # Less than
[[ $a -gt $b ]] # Greater than
[[ "$a" == "$b" ]] # String equal
[[ "$a" != "$b" ]] # String not equal
[[ "$a" =~ regex ]] # Regex match
# Check exit code
[[ $? -eq 0 ]] && echo "Last command succeeded"
HTTP Status Codes
1xx Informational:
| Code | Meaning |
|---|---|
|
Continue |
|
Switching Protocols |
|
Processing (WebDAV) |
2xx Success:
| Code | Meaning |
|---|---|
|
OK |
|
Created |
|
Accepted |
|
No Content |
|
Partial Content |
3xx Redirection:
| Code | Meaning |
|---|---|
|
Moved Permanently |
|
Found (Moved Temporarily) |
|
See Other |
|
Not Modified |
|
Temporary Redirect |
|
Permanent Redirect |
4xx Client Error:
| Code | Meaning |
|---|---|
|
Bad Request |
|
Unauthorized (auth required) |
|
Forbidden (auth insufficient) |
|
Not Found |
|
Method Not Allowed |
|
Request Timeout |
|
Conflict |
|
Gone (permanently) |
|
Payload Too Large |
|
Unsupported Media Type |
|
Unprocessable Entity |
|
Too Many Requests (rate limit) |
5xx Server Error:
| Code | Meaning |
|---|---|
|
Internal Server Error |
|
Not Implemented |
|
Bad Gateway |
|
Service Unavailable |
|
Gateway Timeout |
|
Insufficient Storage |
Handling in scripts:
# Check HTTP status
http_code=$(curl -s -o /dev/null -w "%{http_code}" "$url")
case $http_code in
2??) echo "Success: $http_code" ;;
3??) echo "Redirect: $http_code" ;;
401) echo "Auth required" ;;
403) echo "Forbidden" ;;
404) echo "Not found" ;;
4??) echo "Client error: $http_code" ;;
5??) echo "Server error: $http_code" ;;
*) echo "Unknown: $http_code" ;;
esac
kubectl Exit Codes
| Code | Meaning |
|---|---|
|
Success |
|
General error / failure |
|
Command cannot execute (permission) |
|
Command not found |
|
Killed (OOM or manual) |
Usage:
# Check if resource exists
kubectl get deployment myapp &>/dev/null
[[ $? -eq 0 ]] && echo "Deployment exists"
# Wait with timeout
if ! kubectl wait --for=condition=ready pod -l app=myapp --timeout=60s; then
echo "Pods not ready after 60s"
exit 1
fi
# Apply with error handling
if kubectl apply -f manifest.yaml; then
echo "Applied successfully"
else
echo "Apply failed"
kubectl get events --sort-by=.lastTimestamp | tail -5
fi
Vault Exit Codes
| Code | Meaning |
|---|---|
|
Success |
|
Error (with message) |
|
Usage error (invalid args/options) |
Usage:
# Check Vault status
if vault status &>/dev/null; then
echo "Vault is unsealed and accessible"
else
echo "Vault is sealed or unreachable"
fi
# Read secret with error handling
if ! secret=$(vault kv get -format=json secret/mypath 2>/dev/null); then
echo "Failed to read secret"
exit 1
fi
# Check if secret exists
vault kv get -format=json secret/mypath &>/dev/null
[[ $? -eq 0 ]] && echo "Secret exists"
Docker Exit Codes
Container Exit Codes:
| Code | Meaning |
|---|---|
|
Success |
|
Application error |
|
Docker daemon error |
|
Command cannot invoke (permission) |
|
Command not found in container |
|
Container received signal n |
|
OOM killed or docker kill (SIGKILL) |
|
Segmentation fault |
|
Graceful shutdown (SIGTERM) |
|
Exit status out of range |
Usage:
# Run with exit code capture
docker run myimage mycommand
exit_code=$?
case $exit_code in
0) echo "Success" ;;
125) echo "Docker daemon error" ;;
126) echo "Cannot invoke command (permission)" ;;
127) echo "Command not found in container" ;;
137) echo "OOM killed or docker kill" ;;
143) echo "Graceful shutdown" ;;
*) echo "Exit code: $exit_code" ;;
esac
# Get last container exit code
docker inspect --format='{{.State.ExitCode}}' container_name
# Check container health
docker inspect --format='{{.State.Health.Status}}' container_name
Ansible Exit Codes
| Code | Meaning |
|---|---|
|
Success (all hosts OK) |
|
Error (syntax, config) |
|
One or more hosts failed |
|
One or more hosts unreachable |
|
Parser error |
|
Bad or incomplete options |
|
User interrupted |
|
Unexpected error |
Usage:
# Run playbook with error handling
ansible-playbook site.yml
exit_code=$?
case $exit_code in
0) echo "All hosts OK" ;;
2) echo "Some hosts failed" ;;
3) echo "Some hosts unreachable" ;;
*) echo "Ansible error: $exit_code" ;;
esac
# Retry unreachable hosts
ansible-playbook site.yml --limit @site.retry
Terraform Exit Codes
| Code | Meaning |
|---|---|
|
Success (apply/plan with no changes) |
|
Error (config, apply failed) |
|
Plan has changes (with -detailed-exitcode) |
Usage:
# Check if plan has changes
terraform plan -detailed-exitcode
case $? in
0) echo "No changes" ;;
1) echo "Error" ;;
2) echo "Changes needed" ;;
esac
# Apply with handling
if terraform apply -auto-approve; then
echo "Apply successful"
else
echo "Apply failed"
exit 1
fi
Python Exit Codes
| Code | Meaning |
|---|---|
|
Success (sys.exit(0) or normal termination) |
|
Error (sys.exit(1) or uncaught exception) |
|
Command line syntax error |
Setting exit codes:
#!/usr/bin/env python3
import sys
# Exit success
sys.exit(0)
# Exit error
sys.exit(1)
# Exit with message (goes to stderr, code 1)
sys.exit("Error: something went wrong")
# In scripts
def main():
if error_condition:
print("Error occurred", file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
sys.exit(main())
Checking in shell:
python script.py
[[ $? -eq 0 ]] && echo "Python script succeeded"
netapi Exit Codes
| Code | Meaning |
|---|---|
|
Success |
|
General error (API error, invalid input) |
|
Invalid arguments / usage error |
Usage:
# Check ISE connection
if netapi ise mnt sessions &>/dev/null; then
echo "ISE API accessible"
fi
# Handle specific errors
netapi ise ers endpoint-by-mac "$mac" 2>/dev/null
case $? in
0) echo "Endpoint found" ;;
1) echo "Not found or API error" ;;
2) echo "Invalid MAC format" ;;
esac
Exit Code Best Practices
Script template:
#!/bin/bash
set -euo pipefail # Exit on error, undefined var, pipe fail
# Constants
readonly EX_OK=0
readonly EX_USAGE=64
readonly EX_DATAERR=65
readonly EX_NOINPUT=66
readonly EX_SOFTWARE=70
readonly EX_TEMPFAIL=75
readonly EX_NOPERM=77
readonly EX_CONFIG=78
# Usage
usage() {
echo "Usage: $0 [-h] <input_file>" >&2
exit $EX_USAGE
}
# Error handler
die() {
echo "Error: $1" >&2
exit "${2:-1}"
}
# Cleanup on exit
cleanup() {
rm -f "$TMPFILE" 2>/dev/null || true
}
trap cleanup EXIT
# Main
main() {
[[ $# -lt 1 ]] && usage
local input="$1"
[[ -f "$input" ]] || die "File not found: $input" $EX_NOINPUT
[[ -r "$input" ]] || die "Cannot read: $input" $EX_NOPERM
# Process...
return $EX_OK
}
main "$@"
Wrapper functions:
# Run command and return success/fail
check_success() {
"$@" &>/dev/null
}
# Run command, log on failure
run_or_fail() {
if ! "$@"; then
echo "Command failed: $*" >&2
return 1
fi
}
# Retry command with backoff
retry() {
local max_attempts="${1:-3}"
shift
local attempt=1
while [[ $attempt -le $max_attempts ]]; do
if "$@"; then
return 0
fi
echo "Attempt $attempt failed, retrying..." >&2
sleep $((attempt * 2))
((attempt++))
done
echo "All $max_attempts attempts failed" >&2
return 1
}
# Usage
retry 3 curl -sf "$url"