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
| Variable | Description |
|---|
$0 | Script name |
$1 to $9 | First 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
| Syntax | Description |
|---|
cmd > file | Redirect stdout to file (overwrite) |
cmd >> file | Redirect stdout to file (append) |
cmd 2> file | Redirect stderr to file |
cmd &> file | Redirect both stdout and stderr |
cmd > file 2>&1 | Redirect both (alternative) |
cmd < file | Read stdin from file |
cmd1 | cmd2 | Pipe stdout of cmd1 to stdin of cmd2 |
cmd1 |& cmd2 | Pipe both stdout and stderr |
cmd > /dev/null | Discard 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
| Operator | Description |
|---|
-e file | File exists |
-f file | Regular file |
-d file | Directory |
-s file | File not empty |
-r file | Readable |
-w file | Writable |
-x file | Executable |
-L file | Symbolic link |
-N file | Modified since last read |
file1 -nt file2 | file1 newer than file2 |
file1 -ot file2 | file1 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
| Operator | Description |
|---|
[[ 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
| Operator | Description |
|---|
[ $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.