Bash Scripting Cheat Sheet: Variables, Loops, Functions, and More

6 min read
Intermediate Bash Scripting Linux Shell Cheat Sheet

Quick Answer: Start every script with #!/bin/bash. Make it executable: chmod +x script.sh. Run it: ./script.sh. Variables: name="Sam", use with $name. If statement: if [ "$x" -gt 5 ]; then echo "big"; fi. Loop: for i in 1 2 3; do echo $i; done.

Script Basics

#!/bin/bash
# First line tells the system to use bash
# Make executable: chmod +x script.sh
# Run: ./script.sh

# Exit on error (put this near the top of every script)
set -euo pipefail
Flag What it does
set -e Exit immediately if a command fails
set -u Treat unset variables as errors
set -o pipefail Pipe fails if any command in it fails
set -x Print each command before running (debug)

Variables

# Assign (no spaces around =)
name="Sam"
age=30
path="/var/log"

# Use
echo "Hello $name"
echo "Path is ${path}/nginx"    # Braces for clarity

# Read-only
readonly PI=3.14159

# Command output as variable
today=$(date +%Y-%m-%d)
files=$(ls -1 | wc -l)
ip=$(curl -s ifconfig.me)

# Default value
name=${1:-"World"}              # Use $1, or "World" if not set
port=${PORT:-8080}              # Use $PORT env var, or 8080

# String length
echo ${#name}                   # 3

Special Variables

Variable Meaning
$0 Script name
$1, $2... Arguments
$# Number of arguments
$@ All arguments (as separate words)
$* All arguments (when quoted with "$*", joins with $IFS)
$? Exit code of last command
$$ Current script PID
$! PID of last background command

Conditionals

If / Else

if [ "$age" -gt 18 ]; then
    echo "Adult"
elif [ "$age" -gt 12 ]; then
    echo "Teen"
else
    echo "Child"
fi

Test Operators

Numbers:

Operator Meaning
-eq Equal
-ne Not equal
-gt Greater than
-ge Greater or equal
-lt Less than
-le Less or equal

Strings:

Operator Meaning
= or == Equal
!= Not equal
-z "$var" Is empty
-n "$var" Is not empty

Files:

Operator Meaning
-f file File exists (regular file)
-d dir Directory exists
-e path Exists (any type)
-r file Is readable
-w file Is writable
-x file Is executable
-s file File is not empty
# File checks
if [ -f "/etc/nginx/nginx.conf" ]; then
    echo "Nginx is installed"
fi

if [ ! -d "/var/www/html" ]; then
    mkdir -p /var/www/html
fi

# String checks
if [ -z "$USER" ]; then
    echo "USER is not set"
fi

# Multiple conditions
if [ "$a" -gt 0 ] && [ "$a" -lt 100 ]; then
    echo "Between 0 and 100"
fi

if [ "$x" = "yes" ] || [ "$x" = "y" ]; then
    echo "Confirmed"
fi

Case Statement

case "$1" in
    start)
        echo "Starting..."
        ;;
    stop)
        echo "Stopping..."
        ;;
    restart)
        echo "Restarting..."
        ;;
    *)
        echo "Usage: $0 {start|stop|restart}"
        exit 1
        ;;
esac

Loops

For Loop

# List
for name in Alice Bob Charlie; do
    echo "Hello $name"
done

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

# Range with step
for i in {0..100..5}; do
    echo "$i"
done

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

# Files
for file in *.txt; do
    echo "Processing $file"
done

# Command output
for user in $(cut -d: -f1 /etc/passwd); do
    echo "User: $user"
done

While Loop

count=0
while [ $count -lt 5 ]; do
    echo "Count: $count"
    count=$((count + 1))
done

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

# Infinite loop with break
while true; do
    read -p "Continue? (y/n) " answer
    [ "$answer" = "n" ] && break
done

Until Loop

# Run until condition is true
count=0
until [ $count -ge 5 ]; do
    echo "$count"
    count=$((count + 1))
done

Functions

# Define
greet() {
    local name=$1                # local variable
    echo "Hello, $name!"
}

# Call
greet "Sam"

# Return values (exit codes)
is_running() {
    pgrep -x "$1" > /dev/null
    return $?
}

if is_running nginx; then
    echo "Nginx is running"
fi

# Return strings (echo + command substitution)
get_ip() {
    echo $(hostname -I | awk '{print $1}')
}
my_ip=$(get_ip)

String Operations

str="Hello, World!"

# Substring
echo ${str:0:5}                 # Hello
echo ${str:7}                   # World!

# Replace
echo ${str/World/Bash}          # Hello, Bash!
echo ${str//l/L}                # HeLLo, WorLd!  (all occurrences)

# Remove prefix/suffix
file="backup-2026-04-06.tar.gz"
echo ${file%.tar.gz}            # backup-2026-04-06  (remove suffix)
echo ${file%%.*}                # backup-2026-04-06  (remove longest suffix)
echo ${file#*-}                 # 2026-04-06.tar.gz  (remove prefix)
echo ${file##*-}                # 06.tar.gz          (remove longest prefix)

# Uppercase / lowercase
echo ${str^^}                   # HELLO, WORLD!
echo ${str,,}                   # hello, world!
echo ${str^}                    # Hello, World! (first char upper)

# Length
echo ${#str}                    # 13

Arrays

# Create
fruits=("apple" "banana" "cherry")

# Access
echo ${fruits[0]}               # apple
echo ${fruits[@]}               # All elements
echo ${#fruits[@]}              # Count: 3

# Add
fruits+=("date")

# Loop
for fruit in "${fruits[@]}"; do
    echo "$fruit"
done

# Slice
echo ${fruits[@]:1:2}           # banana cherry

# Delete
unset fruits[1]

# Associative array (dictionary)
declare -A config
config[host]="localhost"
config[port]="8080"
echo ${config[host]}

Input/Output

# Read user input
read -p "Enter name: " name

# Read password (hidden)
read -sp "Password: " pass
echo

# Read with timeout
read -t 5 -p "Quick! Enter something: " answer

# Read with default
read -p "Port [8080]: " port
port=${port:-8080}

# Redirect
echo "text" > file.txt          # Overwrite
echo "more" >> file.txt         # Append
command 2> errors.log           # Stderr to file
command > out.log 2>&1          # Both to file
command &> all.log              # Both (shorthand)
command > /dev/null 2>&1        # Discard all output

Error Handling

# Exit on error
set -e

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

# Trap cleanup on exit
cleanup() {
    rm -f /tmp/myapp.pid
    echo "Cleaned up"
}
trap cleanup EXIT

# Check command success
if ! command -v docker &> /dev/null; then
    echo "Docker is not installed"
    exit 1
fi

# Or pattern
docker ps || { echo "Docker not running"; exit 1; }

Useful Patterns

Script Template

#!/bin/bash
set -euo pipefail

# Usage
usage() {
    echo "Usage: $0 [-v] [-p port] <name>"
    exit 1
}

# Defaults
VERBOSE=false
PORT=8080

# Parse arguments
while getopts "vp:h" opt; do
    case $opt in
        v) VERBOSE=true ;;
        p) PORT=$OPTARG ;;
        h) usage ;;
        *) usage ;;
    esac
done
shift $((OPTIND - 1))

# Require positional argument
NAME=${1:?$(usage)}

# Main
echo "Name: $NAME, Port: $PORT, Verbose: $VERBOSE"

Logging

log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; }
err() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $*" >&2; }

log "Starting backup..."
err "File not found!"

Retry a Command

retry() {
    local max=3 count=0
    until "$@"; do
        count=$((count + 1))
        if [ $count -ge $max ]; then
            echo "Failed after $max attempts"
            return 1
        fi
        echo "Retry $count/$max..."
        sleep 2
    done
}

retry curl -s https://example.com

Progress Bar

for i in $(seq 1 100); do
    printf "\rProgress: [%-50s] %d%%" $(printf '#%.0s' $(seq 1 $((i/2)))) $i
    sleep 0.05
done
echo

One-Liners

# Find and replace in all files
find . -name "*.py" -exec sed -i 's/old/new/g' {} +

# Parallel execution
cat urls.txt | xargs -P 4 -I {} curl -sO {}

# Watch a file for changes
while inotifywait -e modify file.txt; do echo "Changed!"; done

# Simple timer
start=$SECONDS; sleep 5; echo "Took $((SECONDS - start))s"

# Check if root
[ "$(id -u)" -ne 0 ] && echo "Run as root" && exit 1

# Get script directory (works even with symlinks)
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

See Also