Bash Script Basics
Shebang
Every Bash script starts with a shebang (#!) on the first line. It tells the OS which interpreter to use.
#!/usr/bin/env bash
# Preferred — finds bash in PATH, more portable
#!/bin/bash
# Also common — uses bash at fixed location
Making a Script Executable
# Write script
cat > hello.sh << 'EOF'
#!/usr/bin/env bash
echo "Hello, World!"
EOF
# Make executable
chmod +x hello.sh
# Run it
./hello.sh
bash hello.sh # alternative — don't need +x
Script Best Practices (Header)
#!/usr/bin/env bash
set -euo pipefail
# -e : exit on error
# -u : treat unset variables as errors
# -o pipefail : catch errors in pipelines
Variables
Declaring and Using Variables
# Assignment — NO spaces around =
name="Alice"
age=30
greeting="Hello, $name!"
# Using variables
echo $name # simple
echo "${name}" # safer, preferred (explicit boundary)
echo "User: ${name}, Age: ${age}"
Variable Types
# String
message="DevOps is awesome"
# Integer (use for math)
count=10
(( count++ )) # increment
(( count += 5 )) # add 5
echo $count # 16
# Read-only (constant)
readonly MAX_RETRIES=5
declare -r PI=3.14 # same as readonly
# Arrays
fruits=("apple" "banana" "cherry")
echo ${fruits[0]} # apple
echo ${fruits[@]} # all elements
echo ${#fruits[@]} # array length
fruits+=("mango") # append
# Associative arrays (bash 4+)
declare -A config
config["host"]="localhost"
config["port"]="5432"
echo ${config["host"]}
Special Variables
$0 # script name
$1, $2 # positional arguments (first, second...)
$@ # all arguments as separate words
$* # all arguments as single string
$# # number of arguments
$? # exit status of last command (0 = success)
$$ # PID of current script
$! # PID of last background command
$_ # last argument of previous command
Command Substitution
current_date=$(date +%Y-%m-%d)
echo "Today is: $current_date"
files_count=$(ls | wc -l)
echo "Files in directory: $files_count"
# Nested
echo "Disk usage: $(df -h / | tail -1 | awk '{print $5}')"
String Operations
text="Hello, World!"
echo ${#text} # length: 13
echo ${text:0:5} # substring: Hello
echo ${text^^} # uppercase: HELLO, WORLD!
echo ${text,,} # lowercase: hello, world!
echo ${text/World/Bash} # replace: Hello, Bash!
# Default values
name=${1:-"Guest"} # use "Guest" if $1 is unset
port=${PORT:-8080} # use 8080 if $PORT unset
config=${CONFIG:?"CONFIG must be set"} # error if unset
Conditionals
if / elif / else
#!/usr/bin/env bash
score=85
if [ $score -ge 90 ]; then
echo "Grade: A"
elif [ $score -ge 80 ]; then
echo "Grade: B"
elif [ $score -ge 70 ]; then
echo "Grade: C"
else
echo "Grade: F"
fi
Test Conditions
# Numeric comparisons
[ $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
# String comparisons
[ "$a" = "$b" ] # equal
[ "$a" != "$b" ] # not equal
[ -z "$a" ] # empty string
[ -n "$a" ] # non-empty string
# File tests
[ -f "$file" ] # is regular file
[ -d "$dir" ] # is directory
[ -e "$path" ] # exists
[ -r "$file" ] # readable
[ -w "$file" ] # writable
[ -x "$file" ] # executable
[ -s "$file" ] # file size > 0
# Logical operators
[ "$a" = "yes" ] && [ $b -gt 0 ] # AND
[ "$a" = "yes" ] || [ "$b" = "y" ] # OR
! [ -f "$file" ] # NOT
Double Brackets (bash extended)
# [[ ]] is preferred in bash — more powerful
if [[ $name == "Alice" ]]; then
echo "Hello, Alice!"
fi
# Pattern matching
if [[ $filename == *.log ]]; then
echo "It's a log file"
fi
# Regex matching
if [[ $email =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
echo "Valid email"
fi
# No need to quote variables in [[ ]]
if [[ -f $file && -r $file ]]; then
echo "File exists and is readable"
fi
case Statement
#!/usr/bin/env bash
day=$(date +%A)
case $day in
Monday|Tuesday|Wednesday|Thursday|Friday)
echo "Weekday — time to work!"
;;
Saturday|Sunday)
echo "Weekend — time to rest!"
;;
*)
echo "Unknown day"
;;
esac
Loops
for Loop
# Numeric range
for i in {1..5}; do
echo "Iteration $i"
done
# Step (bash 4+)
for i in {0..20..5}; do
echo $i # 0 5 10 15 20
done
# C-style
for (( i=0; i<10; i++ )); do
echo "i = $i"
done
# Iterate over array
servers=("web01" "web02" "db01")
for server in "${servers[@]}"; do
echo "Checking $server..."
ssh $server uptime
done
# Iterate over files
for file in /var/log/*.log; do
echo "Processing: $file"
gzip "$file"
done
# Iterate over command output
for user in $(awk -F: '$3 >= 1000 {print $1}' /etc/passwd); do
echo "Regular user: $user"
done
while Loop
# Basic while
count=1
while [ $count -le 5 ]; do
echo "Count: $count"
(( count++ ))
done
# Read lines from file
while IFS= read -r line; do
echo "Processing: $line"
done < /etc/hosts
# Infinite loop with break
while true; do
if ping -c 1 server.local &>/dev/null; then
echo "Server is up!"
break
fi
echo "Waiting for server..."
sleep 5
done
Loop Control
for i in {1..10}; do
[ $i -eq 5 ] && continue # skip 5
[ $i -eq 8 ] && break # stop at 8
echo $i
done
# Output: 1 2 3 4 6 7
Functions
Defining and Calling Functions
#!/usr/bin/env bash
# Define
greet() {
local name="$1"
local greeting="${2:-Hello}"
echo "$greeting, $name!"
}
# Call
greet "Alice" # Hello, Alice!
greet "Bob" "Hi" # Hi, Bob!
Return Values
# Return status code (0-255)
is_even() {
(( $1 % 2 == 0 )) # returns exit status
}
if is_even 4; then
echo "4 is even"
fi
# Return data via echo
get_hostname() {
echo "$(hostname -s)"
}
host=$(get_hostname)
echo "Running on: $host"
Practical Function Example
#!/usr/bin/env bash
set -euo pipefail
log() {
local level="$1"
shift
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $*" >&2
}
check_command() {
if ! command -v "$1" &>/dev/null; then
log "ERROR" "Required command '$1' not found"
exit 1
fi
}
retry() {
local retries="${1}"; shift
local delay="${1}"; shift
local attempt=0
until "$@"; do
(( attempt++ ))
if (( attempt >= retries )); then
log "ERROR" "Command failed after $retries attempts"
return 1
fi
log "WARN" "Attempt $attempt failed. Retrying in ${delay}s..."
sleep "$delay"
done
}
# Usage
check_command docker
check_command kubectl
retry 3 5 curl -sf https://api.example.com/health
log "INFO" "All checks passed"