The Ultimate Bash Shell Scripting Cheat Sheet: A Complete Reference

Introduction

Bash (Bourne Again SHell) is the default shell for most Linux distributions and macOS. This comprehensive cheat sheet covers essential Bash scripting concepts, syntax, and best practices to help you write effective shell scripts for automation, system administration, and more.

Script Basics

Script Structure

#!/bin/bash
# Script description
# Author: Your Name
# Date: YYYY-MM-DD

# Variables
name="World"

# Main code
echo "Hello, $name!"

exit 0  # Exit successfully

Running Scripts

  • Make executable: chmod +x script.sh
  • Run script: ./script.sh or bash script.sh
  • Run with debugging: bash -x script.sh

Shebang Options

#!/bin/bash         # Standard bash
#!/usr/bin/env bash # More portable across systems
#!/bin/bash -e      # Exit on first error

Variables & Data Types

Variable Declaration

# No spaces around equals sign
name="John"
age=30
readonly PASSWORD="secret"  # Constant variable

# Arrays
fruits=("Apple" "Banana" "Cherry")
declare -A user=([name]="John" [age]=30)  # Associative array

Variable Access

echo "$name"        # John
echo "${name}"      # John (recommended for clarity)
echo "${name}s"     # Johns (prevents ambiguity)

# Array access
echo "${fruits[0]}"  # Apple (single element)
echo "${fruits[@]}"  # All elements
echo "${#fruits[@]}" # Array length

# Associative array
echo "${user[name]}" # John
echo "${!user[@]}"   # All keys

Special Variables

VariableDescription
$0Script name
$1 to $9First 9 positional parameters
${10}10th parameter (and beyond)
$#Number of parameters
$@All parameters (as separate strings)
$*All parameters (as a single string)
$?Exit status of last command
$$Process ID of current shell
$!Process ID of last background command
$_Last argument of previous command

Variable Manipulation

# String length
echo ${#name}  # 4

# Substring extraction (start, length)
echo ${name:1:2}  # oh

# Default values
echo ${var:-default}  # Use default if var not set
echo ${var:=default}  # Set default if var not set
echo ${var:+value}    # Use value if var is set, else nothing
echo ${var:?error}    # Display error if var not set

# Search and replace
echo ${name/o/a}      # Replace first 'o' with 'a'
echo ${name//o/a}     # Replace all 'o' with 'a'
echo ${name/#J/B}     # Replace 'J' at beginning with 'B'
echo ${name/%n/p}     # Replace 'n' at end with 'p'

# Trim patterns
echo ${name#J}        # Remove 'J' from start
echo ${name##J}       # Remove longest match of 'J' from start
echo ${name%n}        # Remove 'n' from end
echo ${name%%n}       # Remove longest match of 'n' from end

# Case modification
echo ${name^}         # Uppercase first character
echo ${name^^}        # Uppercase all characters
echo ${name,}         # Lowercase first character
echo ${name,,}        # Lowercase all characters

Input & Output

User Input

# Basic input
read name
echo "Hello, $name!"

# Prompt with message
read -p "Enter your age: " age

# Silent input (for passwords)
read -s -p "Password: " password

# Input with timeout (5 seconds)
read -t 5 -p "Quick! Type something: " response

# Read into array
read -a colors -p "Enter colors (space-separated): "

Output Formatting

# Basic output
echo "Hello, World!"

# Formatted output
printf "Name: %s, Age: %d\n" "$name" "$age"

# Suppress newline
echo -n "No newline"

# Interpret escape sequences
echo -e "Line 1\nLine 2\tTabbed"

# Color output
echo -e "\033[31mRed text\033[0m"
echo -e "\033[1;32mBold green\033[0m"

# Error output
echo "Error message" >&2

Redirection & Pipes

SyntaxDescription
cmd > fileRedirect stdout to file (overwrite)
cmd >> fileRedirect stdout to file (append)
cmd 2> fileRedirect stderr to file
cmd &> fileRedirect both stdout and stderr
cmd > file 2>&1Redirect both (alternative)
cmd < fileRead stdin from file
cmd1 | cmd2Pipe stdout of cmd1 to stdin of cmd2
cmd1 |& cmd2Pipe both stdout and stderr
cmd > /dev/nullDiscard output

Here Documents & Strings

# Here document (multi-line input)
cat << EOF > file.txt
Line 1
Line 2
Current user: $USER
EOF

# Here string (single-line input)
grep "pattern" <<< "text to search"

Control Structures

Conditionals

If-Else Statement

if [ "$count" -eq 0 ]; then
    echo "Count is zero"
elif [ "$count" -lt 0 ]; then
    echo "Count is negative"
else
    echo "Count is positive"
fi

# Modern test syntax (supports more operators)
if [[ "$string" == *pattern* ]]; then
    echo "String contains pattern"
fi

# Test command exit status
if command -v git &> /dev/null; then
    echo "Git is installed"
fi

Case Statement

case "$option" in
    start|--start)
        echo "Starting service"
        ;;
    stop|--stop)
        echo "Stopping service"
        ;;
    restart|--restart)
        echo "Restarting service"
        ;;
    *)
        echo "Unknown option: $option"
        exit 1
        ;;
esac

Loops

For Loop

# Iterate over values
for name in John Jane Jack; do
    echo "Hello, $name!"
done

# C-style for loop
for ((i=0; i<5; i++)); do
    echo "Count: $i"
done

# Iterate over array
for fruit in "${fruits[@]}"; do
    echo "Fruit: $fruit"
done

# Iterate over files
for file in *.txt; do
    echo "Processing $file"
done

While Loop

# Basic while loop
while [ "$count" -gt 0 ]; do
    echo "Count: $count"
    ((count--))
done

# Read file line by line
while IFS= read -r line; do
    echo "Line: $line"
done < input.txt

# Infinite loop
while true; do
    echo "Press Ctrl+C to exit"
    sleep 1
done

Until Loop

until [ "$count" -eq 0 ]; do
    echo "Count: $count"
    ((count--))
done

Loop Control

# Skip current iteration
for num in {1..10}; do
    if [ $((num % 2)) -eq 0 ]; then
        continue  # Skip even numbers
    fi
    echo "$num"
done

# Exit loop early
for num in {1..10}; do
    if [ "$num" -eq 5 ]; then
        break  # Stop at 5
    fi
    echo "$num"
done

Functions

Function Definition & Usage

# Basic function
hello() {
    echo "Hello, World!"
}

# Alternative syntax
function goodbye() {
    echo "Goodbye, World!"
}

# Call functions
hello
goodbye

Function Parameters

greet() {
    local name="$1"  # First parameter
    local time="$2"  # Second parameter
    echo "Good $time, $name!"
}

# Call with parameters
greet "John" "morning"

Return Values

# Return status code
is_even() {
    if [ $(($1 % 2)) -eq 0 ]; then
        return 0  # Success (true)
    else
        return 1  # Failure (false)
    fi
}

# Return output via echo
get_date() {
    echo $(date +%Y-%m-%d)
}

# Usage
if is_even 4; then
    echo "4 is even"
fi

today=$(get_date)
echo "Today is $today"

Local Variables

calculate() {
    local result=$(($1 + $2))  # Local to function
    echo "Result: $result"
}

calculate 5 3
# 'result' not available here

File Operations & Tests

File Test Operators

OperatorDescription
-e fileFile exists
-f fileRegular file
-d fileDirectory
-s fileFile not empty
-r fileReadable
-w fileWritable
-x fileExecutable
-L fileSymbolic link
-N fileModified since last read
file1 -nt file2file1 newer than file2
file1 -ot file2file1 older than file2

Common File Operations

# Check if file exists
if [ -f "$file" ]; then
    echo "$file exists"
fi

# Check if directory exists
if [ ! -d "$dir" ]; then
    mkdir -p "$dir"
fi

# Read file line by line
while IFS= read -r line || [ -n "$line" ]; do
    echo "Line: $line"
done < "$file"

# Process files in directory
for file in "$dir"/*.txt; do
    [ -f "$file" ] || continue  # Skip if not a file
    echo "Processing $file"
done

String & Number Operations

String Comparison

OperatorDescription
[[ str1 == str2 ]]Strings equal
[[ str1 != str2 ]]Strings not equal
[[ str1 < str2 ]]str1 sorts before str2
[[ str1 > str2 ]]str1 sorts after str2
[[ -z str ]]String is empty
[[ -n str ]]String is not empty
[[ str =~ regex ]]String matches regex

Numeric Comparison

OperatorDescription
[ $a -eq $b ]Equal
[ $a -ne $b ]Not equal
[ $a -lt $b ]Less than
[ $a -le $b ]Less than or equal
[ $a -gt $b ]Greater than
[ $a -ge $b ]Greater than or equal

Arithmetic Operations

# Basic arithmetic (returns result)
result=$((5 + 3))
echo $result  # 8

# Increment/decrement
((count++))
((total--))

# Compound assignment
((sum += 10))
((prod *= 2))

# With variables
a=5
b=3
result=$((a * b + 2))

# Other operations
((power = 2 ** 3))  # Exponentiation
((remainder = 10 % 3))  # Modulus

# Arithmetic conditions
if ((count > 0 && count < 10)); then
    echo "Count is between 1 and 9"
fi

Advanced Features

Command Substitution

# Modern syntax (preferred)
current_date=$(date +%Y-%m-%d)

# Legacy syntax
current_time=`date +%H:%M:%S`

# Nested substitution
file_count=$(find "$dir" -type f | wc -l)

Process Substitution

# Use output of commands as files
diff <(ls dir1) <(ls dir2)

# Redirect multiple outputs
tee >(grep "error" > errors.log) >(grep "warning" > warnings.log) < logfile.txt

Brace Expansion

# Generate sequences
echo {1..5}        # 1 2 3 4 5
echo {a..e}        # a b c d e
echo {1..10..2}    # 1 3 5 7 9

# Generate combinations
echo file{1,2,3}.txt      # file1.txt file2.txt file3.txt
echo {png,jpg,gif}        # png jpg gif
echo {2023..2025}-{01..12} # All months for 3 years

Parameter Expansion

# Array slices
echo "${array[@]:1:2}"  # 2nd and 3rd elements

# Pattern replacement
files=(file1.txt file2.txt file3.jpg)
jpgs=("${files[@]/%.txt/.jpg}")  # Replace .txt with .jpg

# Length of array
echo "${#array[@]}"  # Number of elements

Regular Expressions

# Basic regex match
if [[ "$email" =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]]; then
    echo "Valid email format"
fi

# Capture groups
if [[ "$version" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
    major="${BASH_REMATCH[1]}"
    minor="${BASH_REMATCH[2]}"
    patch="${BASH_REMATCH[3]}"
    echo "Major: $major, Minor: $minor, Patch: $patch"
fi

Error Handling

# Exit on any error
set -e

# Exit on undefined variables
set -u

# Exit if pipe command fails
set -o pipefail

# Combine options
set -euo pipefail

# Trap for cleanup
cleanup() {
    echo "Cleaning up temporary files"
    rm -f "$tmpfile"
}
trap cleanup EXIT

# Error handling for specific commands
command || { echo "Command failed"; exit 1; }

Option Parsing

# Manual option parsing
while [[ $# -gt 0 ]]; do
    case "$1" in
        -v|--verbose)
            verbose=true
            shift
            ;;
        -f|--file)
            file="$2"
            shift 2
            ;;
        -h|--help)
            show_help
            exit 0
            ;;
        *)
            echo "Unknown option: $1"
            exit 1
            ;;
    esac
done

# Using getopts (standard)
while getopts ":hvf:" opt; do
    case $opt in
        h)
            show_help
            exit 0
            ;;
        v)
            verbose=true
            ;;
        f)
            file="$OPTARG"
            ;;
        \?)
            echo "Invalid option: -$OPTARG"
            exit 1
            ;;
        :)
            echo "Option -$OPTARG requires an argument"
            exit 1
            ;;
    esac
done
shift $((OPTIND-1))

Debugging Techniques

Debug Modes

# Debug entire script
#!/bin/bash -x

# Debug specific section
set -x  # Enable debugging
commands_to_debug
set +x  # Disable debugging

# Verbose mode
set -v  # Print shell input lines as read
commands_to_trace
set +v  # Disable verbose mode

Debugging Functions

# Custom debug function
debug() {
    [ "$DEBUG" = "true" ] && echo "DEBUG: $*" >&2
}

# Usage
DEBUG=true
debug "Value of count: $count"

Trace Variables

# Show variable values at each step
echo "Before: count=$count"
((count++))
echo "After: count=$count"

Common Patterns & Best Practices

Script Template

#!/usr/bin/env bash
#
# Script Name: script.sh
# Description: Brief description of what the script does
# Author: Your Name <your.email@example.com>
# Date: YYYY-MM-DD
#
# Usage: ./script.sh [options] <arguments>

set -euo pipefail

# Constants
readonly VERSION="1.0.0"
readonly SCRIPT_NAME=$(basename "$0")

# Functions
show_help() {
    cat << EOF
Usage: $SCRIPT_NAME [options] <argument>

Options:
  -h, --help      Show this help message and exit
  -v, --verbose   Enable verbose output
  -V, --version   Show version information

Examples:
  $SCRIPT_NAME --verbose input.txt
EOF
}

show_version() {
    echo "$SCRIPT_NAME version $VERSION"
}

log() {
    echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*"
}

error() {
    echo "[ERROR] $*" >&2
}

# Parse arguments
verbose=false

while [[ $# -gt 0 ]]; do
    case "$1" in
        -h|--help)
            show_help
            exit 0
            ;;
        -v|--verbose)
            verbose=true
            shift
            ;;
        -V|--version)
            show_version
            exit 0
            ;;
        --)
            shift
            break
            ;;
        -*)
            error "Unknown option: $1"
            show_help
            exit 1
            ;;
        *)
            break
            ;;
    esac
done

# Check required arguments
if [[ $# -lt 1 ]]; then
    error "Missing required argument"
    show_help
    exit 1
fi

input_file="$1"

# Check if file exists
if [[ ! -f "$input_file" ]]; then
    error "File not found: $input_file"
    exit 1
fi

# Main logic
main() {
    log "Starting script"
    
    # Script logic here
    
    log "Script completed successfully"
}

# Cleanup on exit
cleanup() {
    # Cleanup temporary files, etc.
    log "Cleaning up"
}

trap cleanup EXIT

# Run main function
main

Safe File & Directory Handling

# Create temporary files safely
tempfile=$(mktemp)
tempdir=$(mktemp -d)

# Handle spaces in filenames
find . -name "*.txt" -print0 | while IFS= read -r -d $'\0' file; do
    echo "Processing '$file'"
done

# Use safer globbing
shopt -s nullglob  # Empty array if no matches
for file in ./*.txt; do
    echo "Found $file"
done

Error Handling Patterns

# Check if command exists
command -v git &> /dev/null || { echo "Git is required but not installed"; exit 1; }

# Function with error handling
process_file() {
    local file="$1"
    if [[ ! -f "$file" ]]; then
        echo "Error: File '$file' not found" >&2
        return 1
    fi
    
    # Process file
    return 0
}

# Call function and handle errors
if ! process_file "input.txt"; then
    echo "Failed to process file"
    exit 1
fi

Performance Tips

# Avoid unnecessary subshells
# Instead of:
for file in $(find . -name "*.txt"); do
    # ...
done

# Prefer:
find . -name "*.txt" -print0 | while IFS= read -r -d $'\0' file; do
    # ...
done

# Use built-in commands where possible
# Instead of:
count=$(echo "$string" | grep -o "pattern" | wc -l)

# Prefer:
[[ $string =~ pattern ]] && count="${#BASH_REMATCH[@]}"

Resources for Further Learning

Books

  • “The Linux Command Line” by William Shotts
  • “Bash Cookbook” by Carl Albing, JP Vossen, and Cameron Newham
  • “Classic Shell Scripting” by Arnold Robbins and Nelson H.F. Beebe

Online Resources

  • Bash Reference Manual: https://www.gnu.org/software/bash/manual/
  • Bash Guide for Beginners: https://tldp.org/LDP/Bash-Beginners-Guide/html/
  • Advanced Bash-Scripting Guide: https://tldp.org/LDP/abs/html/
  • ShellCheck (script analysis tool): https://www.shellcheck.net/
  • Explain Shell (command explanation): https://explainshell.com/

This comprehensive cheat sheet covers the essential aspects of Bash shell scripting. Remember to test your scripts thoroughly and consider using tools like ShellCheck to identify potential issues.

Scroll to Top