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