As an experienced full-stack developer and sysadmin, exit codes became an indispensable part of my toolbox years ago. While many developers simply use them to check for overall failure, mastering exit codes can radically transform how you script on Linux. Let‘s dig into bash exit codes in-depth so you can level up!

What Are Exit Codes?

First, a quick primer if you‘re unfamiliar with this concept:

An exit code outputs an integer when finishing programs or shell scripts:

$ my_script.sh
# Runs and finishes with exit code

By convention:

  • Code 0 signals overall success
  • Non-zero indicates an error or failure

Bash stores the most recent foreground exit code in a variable called $?:

$ unknownCommand
bash: unknownCommand: command not found 

$ echo $?
127

Here the shell couldn‘t find unknownCommand, setting $? to 127 upon exiting.

Use exit codes synchronously to react to results or asynchronously in logs for debugging.

Standard Exit Codes

While scripts output any integer 1-255, many standard conventions emerged:

Code Meaning
0 Success
1 General error (often incorrect usage)
2 Shell builtin error
126 Command invoked cannot execute (permissions problem or invalid exe)
127 "Command not found" error
128 Invalid argument passed to exit
128+n Fatal error signal "n"

For example:

  • 128+9 means exit code 137, which represents a process terminating due to SIGKILL

This provides a handy baseline, though even common commands often use specialized codes breaking the "standards". Always check docs!

Command-Specific Codes

Consult individual program manuals for specifics. For example grep vs ls:

grep Codes

Code Meaning
0 Match found
1 No match found
2 Syntax or file error

ls Codes

Code Meaning
0 Success
1 Operation not permitted
2 No such file or directory
3 I/O error
4 Internal failure

As you can see, status codes give more insightful data than just binary success/failure.

Setting Exit Codes in Scripts

The exit command explicitly sets return status in bash scripts (overriding defaults bash would set):

#!/bin/bash

if [[ ! -d "$1" ]]; then
   echo "Error: Input dir $1 doesn‘t exist"
   exit 1 
fi

# Rest of script

exit 0

This signals a specific failure if the input directory doesn‘t exist, vs overall success upon normal completion.

Trapping Errors Gracefully

While exit explicitly sets status returns, trap allows handling errors smoothly within a script rather than terminating immediately:

trap ‘echo "Error on line $LINENO"‘ ERR 

someCommand  # Triggers an error
echo "This runs in spite of the error"

We can also chain commands using && and || to conditionally execute logic based on exit codes:

step1 && step2 || handle_error

Only step2 runs if step1 succeeds. Otherwise branch to handle_error.

These constructs allow scripts to react after things go wrong before finishing.

Portability Considerations

While exit codes work across *nix platforms, beware differences integrating with other environments:

  • Windows uses %ERRORLEVEL% instead of $?
  • Older DOS shells often used 1-10 for base errors
  • Luckly Cygwin and newer shells standardize this more cleanly on Windows

If writing cross-platform scripts, test exit code behavior and log the output rather than relying on codes being consistent. Target simpler usage focusing on overall failure rather than granular coding.

Exit Code Best Practices

Now that you understand exit codes more thoroughly, let‘s discuss some best practices leveraging them:

Granular Coding

Rather than manually checking $?:

if [[ $? == 0 ]]; then
   echo "Success!"
fi

Explicitly set codes on failure conditions throughout long scripts:

if [[ $1 == "" ]]; then
   echo "Error: Input parameter required" 
   exit 2
fi

# Rest of script

exit 0

This documents exactly what failed later during debugging.

Tracing Failures

Log traces when branching based on status:

input=$1

validateInput && processInput || handleValidationError 

# Log failure   
handleValidationError() {
  logger "Input $input failed validation"
  exit 1
} 

Reviewing trace logs later simplifies understanding script execution flows.

Annotating Issues

Comment special exit codes used internally at the top of scripts:

#!/bin/bash
#
# Special Codes:  
# 1 - Invalid Parameters
# 2 - File I/O Errors
# 3 - Database Unavailable

# Rest of script...

exit 0

This serves as self-documentation for your future self or other engineers.

Example Exit Code Usage

Let‘s explore a sample script showcasing common exit code patterns:

#!/bin/bash
#
# Special Codes:
# 1 - Input Errors  
# 2 - AWS CLI Failure
# 3 - Git Push Failure 

REPO=$1
COMMIT_MSG=$2  

# Validate Inputs
if [[ $# -ne 2 ]]; then
  echo "Usage: $0 reponame commit_msg"
  exit 1
fi

# Sync Code to S3
aws s3 sync ./src "s3://mybucket/$REPO" && { echo "S3 sync success"; } || { echo "S3 sync failed"; exit 2; }  

# Commit Changes
git add .
git commit -m "$COMMIT_MSG"

# Push Commits 
git push origin main || { echo "Git push failed"; exit 3; }  

exit 0

This script syncs code to S3 then commits and pushes changes with logic handling failures and coding exits appropriately at each stage.

Notice logging relevant diagnostics before exiting on errors.

Zsh vs Bash Exit Codes

As a full stack developer, you may encounter Zsh in some environments rather than Bash. Does anything differ regarding exit codes?

In essence: no. Zsh adheres closely to Bash and POSIX standards. For example:

% bogusCommand
zsh: command not found: bogusCommand

% echo $?
127

All exit code behavior aligns. But note Zsh does introduce new failure code complexity in intricacies like:

  • New signals numbers
  • Configuring SIGPIPE behavior
  • Process substitution hands signals differently

So while codes work consistently, under the hood additional Zsh nuance exists in some signal handling.

Just take care if mixing Zsh idioms into legacy Bash scripts. Test for portability.

Final Thoughts

I hope this guide provided an expert-level overview into leveraging exit codes effectively!

Mastering exit code usage takes time. Implement gradually and enable traces for insight into script flow failures.

Soon creatively coding exits will become second-nature, allowing incredibly resilient automation!

Let me know if you have any other questions about wielding exit codes like a Linux wizard!

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *