Exit Codes

Every command returns an exit code. 0 means success. Anything else means failure. Your scripts should do the same.

Checking Exit Codes

The special variable $? holds the last exit code:

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

Common Exit Codes

CodeMeaning
0Success
1General error
2Misuse of command
126Permission denied
127Command not found
128+nKilled by signal n
130Ctrl+C (SIGINT)

Using exit in Scripts

hljs bash
#!/bin/bash

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

echo "Processing $1..."
# ... do work ...

exit 0  # explicit success

Exit 0 Is Optional

Scripts exit with the last command's exit code by default. Explicit exit 0 at the end is good practice for clarity.

Conditional Execution

Exit codes power && and ||:

Terminal
$mkdir test && echo 'Created'
Created
$mkdir test || echo 'Failed'
mkdir: cannot create directory 'test': File exists Failed
  • && runs next command only if previous succeeded (exit 0)
  • || runs next command only if previous failed (exit non-0)

Exit on Error Pattern

hljs bash
#!/bin/bash

# Common pattern
command1 || { echo "command1 failed"; exit 1; }
command2 || { echo "command2 failed"; exit 1; }
command3 || { echo "command3 failed"; exit 1; }

Or use set -e:

hljs bash
#!/bin/bash
set -e  # Exit on any error

command1  # Script exits if this fails
command2  # Only runs if command1 succeeded
command3

Check Specific Exit Codes

hljs bash
#!/bin/bash

grep "pattern" file.txt
exit_code=$?

case $exit_code in
    0)
        echo "Pattern found"
        ;;
    1)
        echo "Pattern not found"
        ;;
    2)
        echo "Error occurred"
        ;;
esac

Functions and Exit Codes

hljs bash
#!/bin/bash

check_disk_space() {
    local usage=$(df / | awk 'NR==2 {print $5}' | tr -d '%')

    if [[ $usage -gt 90 ]]; then
        return 1  # Failure - disk nearly full
    fi
    return 0  # Success
}

if check_disk_space; then
    echo "Disk space OK"
else
    echo "Warning: Disk nearly full"
    exit 1
fi

Practical Script Example

hljs bash
#!/bin/bash
# deploy.sh - Deployment with proper exit codes

set -e  # Exit on error

log() { echo "[$(date '+%H:%M:%S')] $1"; }

# Validate environment
[[ -n "$DEPLOY_PATH" ]] || { echo "DEPLOY_PATH not set"; exit 1; }
[[ -d "$DEPLOY_PATH" ]] || { echo "Deploy path doesn't exist"; exit 1; }

log "Starting deployment..."

# Each command must succeed
log "Pulling latest code..."
git pull origin main

log "Installing dependencies..."
npm install

log "Building..."
npm run build

log "Restarting service..."
sudo systemctl restart myapp

log "Deployment complete!"
exit 0
Terminal
$./deploy.sh
[10:30:45] Starting deployment... ...
$echo $?
0
$
$# If npm install fails:
$./deploy.sh
[10:30:45] Starting deployment... [10:30:46] Installing dependencies... npm ERR! ...
$echo $?
1

Pipeline Exit Codes

By default, pipeline exit code is from the last command:

Terminal
$false | true
$echo $?
0 (true succeeded)

To check all commands in a pipeline:

hljs bash
#!/bin/bash
set -o pipefail  # Pipeline fails if ANY command fails

false | true
echo $?  # Now returns 1
Knowledge Check

What does exit code 0 mean?

Quick Reference

SyntaxPurpose
$?Last exit code
exit 0Exit successfully
exit 1Exit with error
set -eExit on any error
set -o pipefailPipeline fails on any error
cmd && nextRun next if cmd succeeds
cmd || nextRun next if cmd fails

Key Takeaways

  • Exit code 0 = success, non-zero = failure
  • $? contains the last exit code
  • Always exit 1 (or other non-zero) on errors
  • Use set -e for strict error handling
  • && chains successful commands
  • || provides fallback on failure
  • Check exit codes when the reason for failure matters

Next: comprehensive error handling.