Skip to content

Bash Programming Cheat Sheet

Bash (Bourne Again SHell) is a powerful scripting language for Linux automation, system administration, and DevOps workflows.


Script Basics

Shebang and Execution

#!/bin/bash
# This is a comment

echo "Hello, World!"

Make executable and run:

chmod +x script.sh
./script.sh

Strict Mode

#!/bin/bash
set -euo pipefail  # Exit on error, undefined vars, pipe failures
IFS=$'\n\t'        # Set safe Internal Field Separator

Variables

Declaration and Usage

# Variable assignment (no spaces!)
name="Alice"
age=25

# Using variables
echo "Name: $name"
echo "Age: ${age}"

# Command substitution
today=$(date +%Y-%m-%d)
files=`ls`  # Old style (not recommended)

# Readonly variable
readonly PI=3.14159

# Environment variable
export PATH="/usr/local/bin:$PATH"

# Unset variable
unset name

Variable Scopes

# Global variable
global_var="I'm global"

function test() {
    local local_var="I'm local"
    echo "$global_var"  # Works
    echo "$local_var"   # Works
}

echo "$local_var"  # Empty (out of scope)

Special Variables

$0    # Script name
$1-$9 # Arguments 1-9
$#    # Number of arguments
$@    # All arguments (as array)
$*    # All arguments (as single string)
$?    # Exit status of last command
$$    # Current process ID
$!    # Last background process ID
$_    # Last argument of previous command

Data Types and Operators

Arithmetic Operations

# Using $(( ))
((a = 5 + 3))
((b = a * 2))
((c++))
echo $((10 / 3))  # 3 (integer division)

# Using let
let "sum = 5 + 3"
let "result = sum * 2"

# Using expr (old method)
result=`expr 5 + 3`

# Operators
+  -  *  /  %      # Arithmetic
() ** ++ --        # Grouping, power, increment, decrement

Comparison Operators

# Numeric comparisons
-eq  # Equal
-ne  # Not equal
-gt  # Greater than
-ge  # Greater or equal
-lt  # Less than
-le  # Less or equal

# Example
if [ $age -ge 18 ]; then
    echo "Adult"
fi

String Operations

# String comparisons
=   or ==    # Equal
!=           # Not equal
<            # Less than (alphabetically)
>            # Greater than
-z           # Empty string
-n           # Not empty string

# Examples
if [ "$str1" = "$str2" ]; then
    echo "Equal"
fi

if [ -z "$empty" ]; then
    echo "String is empty"
fi

# String length
echo ${#string}

# Substring
text="Hello World"
echo ${text:0:5}     # "Hello"
echo ${text:6}       # "World"

# Replace
echo ${text/World/Bash}     # Replace first
echo ${text//o/O}           # Replace all

# Remove prefix/suffix
file="document.txt"
echo ${file%.txt}    # "document"
echo ${file#doc}     # "ument.txt"
echo ${file%.*}      # "document"
echo ${file##*.}     # "txt"

# Case conversion
echo ${text^^}       # HELLO WORLD
echo ${text,,}       # hello world
echo ${text^}        # Hello world (first char)

Logical Operators

&&   # AND
||   # OR
!    # NOT

# Examples
if [ $a -gt 5 ] && [ $b -lt 10 ]; then
    echo "Both conditions true"
fi

if [[ $a > 5 && $b < 10 ]]; then
    echo "Using [[ ]] (preferred)"
fi

Conditionals

If Statement

# Basic if
if [ condition ]; then
    commands
fi

# If-else
if [ condition ]; then
    commands
else
    commands
fi

# If-elif-else
if [ "$age" -lt 13 ]; then
    echo "Child"
elif [ "$age" -lt 18 ]; then
    echo "Teen"
else
    echo "Adult"
fi

# One-liner
[ $a -eq 1 ] && echo "One" || echo "Not one"

Test Commands

# [ ] vs [[ ]]
[ $a = $b ]      # POSIX, requires quotes
[[ $a = $b ]]    # Bash, no quote needed, supports regex

# File tests
-e file    # Exists
-f file    # Regular file
-d file    # Directory
-s file    # Not empty
-r file    # Readable
-w file    # Writable
-x file    # Executable
-L file    # Symbolic link
-S file    # Socket
-p file    # Named pipe

# Examples
if [ -f "file.txt" ]; then
    echo "File exists"
fi

if [[ -r "config.cfg" && -w "config.cfg" ]]; then
    echo "Can read and write"
fi

Case Statement

case $variable in
    pattern1)
        commands
        ;;
    pattern2|pattern3)
        commands
        ;;
    *)
        default commands
        ;;
esac

# Example
read -p "Enter fruit: " fruit
case $fruit in
    apple)
        echo "Red or green"
        ;;
    banana)
        echo "Yellow"
        ;;
    orange|lemon|lime)
        echo "Citrus"
        ;;
    *)
        echo "Unknown fruit"
        ;;
esac

Loops

For Loop

# List iteration
for item in apple banana cherry; do
    echo "$item"
done

# Range
for i in {1..10}; do
    echo "$i"
done

# Step range
for i in {0..20..2}; do
    echo "$i"  # 0, 2, 4, ..., 20
done

# C-style
for ((i=0; i<10; i++)); do
    echo "$i"
done

# File iteration
for file in *.txt; do
    echo "Processing: $file"
done

# Array iteration
fruits=(apple banana orange)
for fruit in "${fruits[@]}"; do
    echo "$fruit"
done

# With index
for i in "${!fruits[@]}"; do
    echo "$i: ${fruits[$i]}"
done

While Loop

# Basic while
count=1
while [ $count -le 5 ]; do
    echo "Count: $count"
    ((count++))
done

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

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

Until Loop

count=0
until [ $count -ge 5 ]; do
    echo "$count"
    ((count++))
done

Loop Control

# Break - exit loop
for i in {1..10}; do
    [ $i -eq 5 ] && break
    echo "$i"
done

# Continue - skip iteration
for i in {1..10}; do
    [ $((i % 2)) -eq 0 ] && continue
    echo "$i"  # Only odd numbers
done

Functions

Function Definition

# Method 1
function greet() {
    echo "Hello, $1!"
}

# Method 2 (preferred)
greet() {
    echo "Hello, $1!"
}

# Call function
greet "Alice"

Function Arguments

process_file() {
    local filename=$1
    local option=${2:-default}  # Default value

    echo "Processing: $filename"
    echo "Option: $option"
}

process_file "data.txt" "verbose"

Return Values

# Return exit code (0-255)
is_valid() {
    [ -f "$1" ] && return 0 || return 1
}

if is_valid "file.txt"; then
    echo "Valid"
fi

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

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

# Multiple return values
get_user_info() {
    echo "John Doe 30"
}

read name surname age < <(get_user_info)

Recursive Functions

factorial() {
    local n=$1
    if [ $n -le 1 ]; then
        echo 1
    else
        local prev=$(factorial $((n-1)))
        echo $((n * prev))
    fi
}

result=$(factorial 5)  # 120

Arrays

Indexed Arrays

# Declaration
arr=(apple banana cherry)
arr[3]="date"

# Access elements
echo ${arr[0]}       # First element
echo ${arr[-1]}      # Last element
echo ${arr[@]}       # All elements
echo ${arr[*]}       # All elements (different quoting)

# Array length
echo ${#arr[@]}      # Number of elements
echo ${#arr[0]}      # Length of first element

# Add elements
arr+=(elderberry)
arr[10]="fig"        # Sparse array

# Remove element
unset arr[1]

# Slicing
echo ${arr[@]:1:2}   # Elements from index 1, count 2

# Iteration
for item in "${arr[@]}"; do
    echo "$item"
done

# With indices
for i in "${!arr[@]}"; do
    echo "$i: ${arr[$i]}"
done

Associative Arrays (Dictionaries)

# Declare
declare -A colors

# Assign
colors[apple]="red"
colors[banana]="yellow"
colors[grape]="purple"

# Access
echo ${colors[apple]}

# All keys
echo ${!colors[@]}

# All values
echo ${colors[@]}

# Iterate
for fruit in "${!colors[@]}"; do
    echo "$fruit is ${colors[$fruit]}"
done

# Check if key exists
if [[ -v colors[apple] ]]; then
    echo "Apple exists"
fi

Input/Output

Reading Input

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

# With prompt
read -p "Enter age: " age

# Silent input (passwords)
read -sp "Password: " password
echo

# Multiple variables
read -p "Enter first and last name: " first last

# Read array
read -a words
echo "First word: ${words[0]}"

# Read with timeout
read -t 5 -p "Quick (5s): " answer

# Read single character
read -n 1 -p "Press any key"

Output

# Echo
echo "Simple output"
echo -n "No newline"
echo -e "Tab:\tNewline:\n"

# Printf (formatted)
printf "Name: %s, Age: %d\n" "Alice" 25
printf "%.2f\n" 3.14159

# Redirect output
echo "text" > file.txt      # Overwrite
echo "text" >> file.txt     # Append
echo "error" >&2            # To stderr

# Here document
cat << EOF
Multiple
Lines
EOF

# Here string
wc -l <<< "Count this line"

File Operations

Reading Files

# Read entire file
content=$(cat file.txt)

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

# Read into array
mapfile -t lines < file.txt
readarray -t lines < file.txt  # Alternative

Writing Files

# Overwrite
echo "New content" > file.txt

# Append
echo "More content" >> file.txt

# Multi-line write
cat > config.txt << EOF
setting1=value1
setting2=value2
EOF

File Testing

if [ -e file.txt ]; then
    echo "Exists"
fi

if [ ! -f file.txt ]; then
    echo "Not a regular file"
    exit 1
fi

# Combine checks
if [[ -f file.txt && -r file.txt ]]; then
    cat file.txt
fi

Process Management

Running Commands

# Background process
long_command &
pid=$!
echo "Running in background: $pid"

# Wait for background process
wait $pid

# Command grouping
(cd /tmp && ls)  # Subshell
{ cd /tmp; ls; }  # Current shell

# Conditional execution
mkdir mydir && cd mydir
rm file.txt || echo "Delete failed"

Pipelines

# Pipe output
ls -l | grep ".txt"

# Pipe to multiple commands
cat file.txt | grep "error" | sort | uniq

# Tee (output to file and stdout)
ls -l | tee output.txt

# Process substitution
diff <(ls dir1) <(ls dir2)

Error Handling

Exit Codes

# Check last command
if [ $? -eq 0 ]; then
    echo "Success"
else
    echo "Failed"
fi

# Or directly
if command; then
    echo "Success"
fi

# Set exit code
exit 0  # Success
exit 1  # Error

Error Handling Patterns

# Exit on error
set -e

# Exit on undefined variable
set -u

# Exit on pipe failure
set -o pipefail

# Disable temporarily
set +e
risky_command
set -e

# Trap errors
trap 'echo "Error on line $LINENO"' ERR

# Trap exit
trap 'cleanup_function' EXIT

# Trap signals
trap 'echo "Interrupted"; exit' INT TERM

Debugging

Debug Techniques

# Print each command
set -x
commands here
set +x

# Verbose mode
set -v

# Run script in debug mode
bash -x script.sh

# Check syntax only
bash -n script.sh

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

debug "This is a debug message"

Practical Patterns

Argument Parsing

#!/bin/bash

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

Configuration Files

# Load config
if [ -f config.sh ]; then
    source config.sh
    # or
    . config.sh
fi

# Parse INI-style config
while IFS='=' read -r key value; do
    [[ $key =~ ^[[:space:]]*# ]] && continue
    [[ -z $key ]] && continue
    declare "$key=$value"
done < config.ini

Logging

#!/bin/bash

LOG_FILE="/var/log/script.log"

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

log "Script started"
log "INFO" "Processing files..."
log "ERROR" "Failed to connect"

Best Practices

Script Template

#!/bin/bash
#
# Script: script_name.sh
# Description: What this script does
# Author: Your Name
# Date: 2025-02-08
#

set -euo pipefail
IFS=$'\n\t'

# Constants
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly SCRIPT_NAME="$(basename "$0")"

# Functions
usage() {
    cat << EOF
Usage: $SCRIPT_NAME [OPTIONS]

OPTIONS:
    -h, --help     Show help
    -v, --verbose  Verbose output
EOF
}

main() {
    # Main logic
    echo "Running..."
}

# Run
main "$@"

Coding Standards

# Use meaningful names
user_count=10        # Good
x=10                 # Bad

# Quote variables
rm "$file"           # Good
rm $file             # Bad (breaks with spaces)

# Use [[ ]] over [ ]
if [[ $var == "value" ]]; then
    echo "Match"
fi

# UPPERCASE for constants
readonly MAX_RETRIES=3

# lowercase for local variables
local temp_file="/tmp/data"

# Check if variable is set
if [[ -n "${VAR:-}" ]]; then
    echo "VAR is set"
fi

# Use functions for reusable code
# Use comments for complex logic
# Keep lines under 80 characters

Quick Reference

# Variables
var="value"
echo "$var"
readonly CONST="constant"

# Arithmetic
((result = 5 + 3))
echo $((10 / 3))

# Conditionals
if [[ condition ]]; then
    commands
elif [[ condition ]]; then
    commands
else
    commands
fi

# Loops
for i in {1..10}; do
    echo "$i"
done

while [[ condition ]]; do
    commands
done

# Functions
function_name() {
    local var=$1
    echo "$var"
}

# Arrays
arr=(item1 item2 item3)
echo "${arr[0]}"
echo "${arr[@]}"

# Strings
${#string}              # Length
${string:pos:len}       # Substring
${string//find/replace} # Replace all

# File tests
-e file  # Exists
-f file  # Is file
-d file  # Is directory
-r file  # Readable

# Exit codes
$?       # Last exit code
exit 0   # Exit success
exit 1   # Exit failure