Learn to write reliable, maintainable shell scripts. From basic syntax to advanced patterns.
Script Basics
The Shebang
#!/bin/bash
# This tells the system to use bash
#!/usr/bin/env bash
# More portable - finds bash in PATHRunning Scripts
# Make executable and run
chmod +x script.sh
./script.sh
# Run with bash directly
bash script.sh
# Run in current shell (for sourcing)
source script.sh
# or
. script.shVariables
Basic Variables
# Assignment (no spaces around =)
name="John"
count=42
# Using variables
echo "Hello, $name"
echo "Count: ${count}" # Braces recommended
# Command substitution
today=$(date +%Y-%m-%d)
files=$(ls *.txt)
# Arithmetic
result=$((5 + 3))
((count++))Special Variables
$0 # Script name
$1-$9 # Positional parameters
$# # Number of arguments
$@ # All arguments (as separate words)
$* # All arguments (as single word)
$? # Exit status of last command
$$ # Current process ID
$! # PID of last background processVariable Operations
# Default values
${var:-default} # Use default if var unset
${var:=default} # Set and use default if var unset
${var:?error} # Exit with error if var unset
# String operations
${#var} # Length
${var:0:5} # Substring (first 5 chars)
${var#pattern} # Remove shortest prefix match
${var##pattern} # Remove longest prefix match
${var%pattern} # Remove shortest suffix match
${var%%pattern} # Remove longest suffix match
${var/old/new} # Replace first match
${var//old/new} # Replace all matchesArrays
# Indexed arrays
fruits=("apple" "banana" "cherry")
echo "${fruits[0]}" # First element
echo "${fruits[@]}" # All elements
echo "${#fruits[@]}" # Array length
fruits+=("date") # Append
# Associative arrays (bash 4+)
declare -A colors
colors[red]="#FF0000"
colors[green]="#00FF00"
echo "${colors[red]}"
echo "${!colors[@]}" # All keysControl Flow
Conditionals
# if statement
if [ "$count" -gt 10 ]; then
echo "Greater than 10"
elif [ "$count" -eq 10 ]; then
echo "Equals 10"
else
echo "Less than 10"
fi
# Modern test syntax
if [[ "$name" == "John" ]]; then
echo "Hello John"
fi
# File tests
if [[ -f "$file" ]]; then
echo "File exists"
fi
if [[ -d "$dir" ]]; then
echo "Directory exists"
fiTest Operators
# String comparisons (use [[ ]] for these)
[[ "$a" == "$b" ]] # Equal
[[ "$a" != "$b" ]] # Not equal
[[ -z "$a" ]] # Empty
[[ -n "$a" ]] # Not empty
# Numeric comparisons (use [ ] or (( )))
[ "$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
# File tests
[ -f "$f" ] # Regular file exists
[ -d "$d" ] # Directory exists
[ -r "$f" ] # Readable
[ -w "$f" ] # Writable
[ -x "$f" ] # Executable
[ -s "$f" ] # File not empty
# Logical operators
[[ cond1 && cond2 ]] # AND
[[ cond1 || cond2 ]] # OR
[[ ! cond ]] # NOTLoops
# For loop
for item in apple banana cherry; do
echo "$item"
done
# C-style for loop
for ((i=0; i<10; i++)); do
echo "$i"
done
# Loop over files
for file in *.txt; do
echo "Processing $file"
done
# Loop over array
for fruit in "${fruits[@]}"; do
echo "$fruit"
done
# While loop
while [ "$count" -lt 10 ]; do
echo "$count"
((count++))
done
# Read file line by line
while IFS= read -r line; do
echo "$line"
done < file.txt
# Infinite loop
while true; do
# ...
sleep 1
doneCase Statement
case "$1" in
start)
echo "Starting..."
;;
stop)
echo "Stopping..."
;;
restart)
echo "Restarting..."
;;
*)
echo "Usage: $0 {start|stop|restart}"
exit 1
;;
esacFunctions
# Define function
greet() {
local name="$1" # Local variable
echo "Hello, $name"
}
# Call function
greet "World"
# Return values (exit status)
is_valid() {
if [[ "$1" =~ ^[0-9]+$ ]]; then
return 0 # Success
else
return 1 # Failure
fi
}
if is_valid "123"; then
echo "Valid number"
fi
# Return string (via stdout)
get_date() {
date +%Y-%m-%d
}
today=$(get_date)Error Handling
Exit Status and set Options
#!/bin/bash
set -e # Exit on error
set -u # Exit on undefined variable
set -o pipefail # Exit on pipe failure
# Combined:
set -euo pipefail
# Check command success
if ! command; then
echo "Command failed"
exit 1
fi
# Trap errors
trap 'echo "Error on line $LINENO"' ERR
# Cleanup on exit
cleanup() {
rm -f /tmp/myfile.tmp
}
trap cleanup EXITInput Validation
#!/bin/bash
set -euo pipefail
# Require arguments
if [ "$#" -lt 1 ]; then
echo "Usage: $0 <filename>"
exit 1
fi
# Validate file exists
if [ ! -f "$1" ]; then
echo "Error: File '$1' not found"
exit 1
fiBest Practices
Script Template
#!/usr/bin/env bash
#
# Description: Brief description of script
# Usage: script.sh [options] <arguments>
#
set -euo pipefail
# Constants
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly SCRIPT_NAME="$(basename "$0")"
# Default values
VERBOSE=false
# Functions
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
}
error() {
log "ERROR: $*" >&2
exit 1
}
usage() {
cat <<EOF
Usage: $SCRIPT_NAME [options] <argument>
Options:
-v, --verbose Enable verbose output
-h, --help Show this help message
Arguments:
argument Description of argument
EOF
}
# Parse arguments
while [[ "$#" -gt 0 ]]; do
case "$1" in
-v|--verbose)
VERBOSE=true
shift
;;
-h|--help)
usage
exit 0
;;
*)
break
;;
esac
done
# Validate arguments
[[ "$#" -lt 1 ]] && { usage; exit 1; }
# Main logic
main() {
log "Starting..."
# Your code here
log "Done."
}
main "$@" beginner Shell Scripting 45 min read
Related Tutorials
bashshell scriptingscriptsautomationbash variablesbash functions