Your First Script
A shell script is a text file containing a sequence of commands that the shell executes. Every script begins with a shebang line that tells the system which interpreter to use:
#!/bin/bash
# This is a comment - it explains what the script does
echo "Hello, World!"
echo "Today is $(date +%A), $(date +%B) $(date +%d), $(date +%Y)"
Making a Script Executable
# Create the script file
nano myscript.sh
# Make it executable
chmod +x myscript.sh
# Run it
./myscript.sh
# Alternative: run with bash explicitly (no chmod needed)
bash myscript.sh
Variables
Variables store data for use throughout your script. In Bash, variables are assigned without spaces around the equals sign and accessed with a $ prefix:
#!/bin/bash
# Variable assignment (NO spaces around =)
name="Alice"
age=30
distro="Ubuntu"
# Using variables
echo "Name: $name"
echo "Age: $age"
echo "$name uses $distro"
# Curly braces for clarity (recommended)
echo "${name}'s system runs ${distro}"
# Command substitution - store command output in a variable
current_date=$(date +%Y-%m-%d)
file_count=$(ls -1 | wc -l)
kernel=$(uname -r)
echo "Date: $current_date"
echo "Files in current directory: $file_count"
echo "Kernel version: $kernel"
# Read-only variables
readonly PI=3.14159
# PI=3.0 # This would cause an error
# Arithmetic
count=5
count=$((count + 1))
result=$((10 * 5 + 3))
echo "Count: $count, Result: $result"
Special Variables
#!/bin/bash
echo "Script name: $0"
echo "First argument: $1"
echo "Second argument: $2"
echo "All arguments: $@"
echo "Number of arguments: $#"
echo "Exit status of last command: $?"
echo "Process ID of this script: $$"
echo "PID of last background process: $!"
User Input
#!/bin/bash
# Basic input
read -p "Enter your name: " username
echo "Hello, $username!"
# Silent input (for passwords)
read -sp "Enter password: " password
echo # newline after silent input
echo "Password length: ${#password} characters"
# Input with default value
read -p "Enter shell [/bin/bash]: " shell
shell=${shell:-/bin/bash}
echo "Shell set to: $shell"
# Input with timeout
read -t 10 -p "Quick! Enter a number (10s): " num
Conditionals
#!/bin/bash
# if/elif/else
age=25
if [ "$age" -lt 18 ]; then
echo "Minor"
elif [ "$age" -lt 65 ]; then
echo "Adult"
else
echo "Senior"
fi
# String comparisons
name="Alice"
if [ "$name" = "Alice" ]; then
echo "Hello, Alice!"
fi
if [ -z "$empty_var" ]; then
echo "Variable is empty or unset"
fi
if [ -n "$name" ]; then
echo "Variable is not empty"
fi
# File tests
if [ -f "/etc/passwd" ]; then
echo "/etc/passwd exists and is a regular file"
fi
if [ -d "/home" ]; then
echo "/home is a directory"
fi
if [ -r "/etc/hostname" ]; then
echo "/etc/hostname is readable"
fi
if [ -x "/usr/bin/bash" ]; then
echo "Bash is executable"
fi
# Combined conditions with && (AND) and || (OR)
if [ -f "/etc/passwd" ] && [ -r "/etc/passwd" ]; then
echo "/etc/passwd exists and is readable"
fi
# Modern double-bracket syntax (preferred in Bash)
if [[ "$name" == A* ]]; then
echo "Name starts with A"
fi
if [[ "$age" -ge 18 && "$age" -le 65 ]]; then
echo "Working age"
fi
Case Statements
#!/bin/bash
read -p "Enter a fruit: " fruit
case "$fruit" in
apple)
echo "Apples are red or green"
;;
banana)
echo "Bananas are yellow"
;;
orange|tangerine)
echo "Citrus fruit!"
;;
*)
echo "Unknown fruit: $fruit"
;;
esac
Loops
#!/bin/bash
# For loop - iterating over a list
for color in red green blue yellow; do
echo "Color: $color"
done
# For loop - iterating over files
for file in /var/log/*.log; do
echo "Log file: $file ($(wc -l < "$file") lines)"
done
# C-style for loop
for ((i = 1; i <= 5; i++)); do
echo "Iteration: $i"
done
# While loop
count=1
while [ "$count" -le 10 ]; do
echo "Count: $count"
count=$((count + 1))
done
# Reading a file line by line
while IFS= read -r line; do
echo "Line: $line"
done < /etc/hostname
# Until loop (runs while condition is false)
num=1
until [ "$num" -gt 5 ]; do
echo "Number: $num"
num=$((num + 1))
done
# Loop control
for i in $(seq 1 100); do
if [ "$i" -eq 5 ]; then
continue # skip iteration
fi
if [ "$i" -eq 10 ]; then
break # exit loop
fi
echo "$i"
done
Functions
#!/bin/bash
# Define a function
greet() {
local name="$1" # local variable (scoped to function)
local time_of_day="$2"
echo "Good $time_of_day, $name!"
}
# Call the function
greet "Alice" "morning"
greet "Bob" "evening"
# Function with return value
is_root() {
if [ "$(id -u)" -eq 0 ]; then
return 0 # success (true)
else
return 1 # failure (false)
fi
}
if is_root; then
echo "Running as root"
else
echo "Not running as root"
fi
# Function that outputs a value (captured with command substitution)
get_disk_usage() {
df -h / | awk 'NR==2 {print $5}'
}
usage=$(get_disk_usage)
echo "Root partition usage: $usage"
Arrays
#!/bin/bash
# Declare an array
fruits=("apple" "banana" "cherry" "date")
# Access elements
echo "First fruit: ${fruits[0]}"
echo "Third fruit: ${fruits[2]}"
# All elements
echo "All fruits: ${fruits[@]}"
# Array length
echo "Number of fruits: ${#fruits[@]}"
# Add an element
fruits+=("elderberry")
# Loop through an array
for fruit in "${fruits[@]}"; do
echo "- $fruit"
done
# Loop with index
for i in "${!fruits[@]}"; do
echo "$i: ${fruits[$i]}"
done
# Remove an element
unset 'fruits[1]'
# Associative arrays (Bash 4+)
declare -A capitals
capitals[France]="Paris"
capitals[Germany]="Berlin"
capitals[Japan]="Tokyo"
echo "Capital of France: ${capitals[France]}"
for country in "${!capitals[@]}"; do
echo "$country -> ${capitals[$country]}"
done
Practical Examples
Backup Script
#!/bin/bash
# Simple backup script
backup_dir="/home/$(whoami)/backups"
source_dir="/home/$(whoami)/documents"
timestamp=$(date +%Y%m%d_%H%M%S)
backup_file="backup_${timestamp}.tar.gz"
# Create backup directory if it does not exist
mkdir -p "$backup_dir"
# Create compressed archive
tar -czf "${backup_dir}/${backup_file}" -C "$(dirname "$source_dir")" "$(basename "$source_dir")"
if [ $? -eq 0 ]; then
echo "Backup created: ${backup_dir}/${backup_file}"
echo "Size: $(du -h "${backup_dir}/${backup_file}" | cut -f1)"
else
echo "ERROR: Backup failed!" >&2
exit 1
fi
# Remove backups older than 30 days
find "$backup_dir" -name "backup_*.tar.gz" -mtime +30 -delete
echo "Old backups cleaned up."
System Health Check
#!/bin/bash
echo "=== System Health Report ==="
echo "Date: $(date)"
echo "Hostname: $(hostname)"
echo ""
echo "--- CPU Load ---"
uptime | awk -F'load average:' '{print $2}'
echo ""
echo "--- Memory Usage ---"
free -h | grep -E "Mem|Swap"
echo ""
echo "--- Disk Usage ---"
df -h | grep -E "^/dev" | awk '{print $1, $5, "used of", $2}'
echo ""
echo "--- Top 5 Processes by CPU ---"
ps aux --sort=-%cpu | head -6
echo ""
echo "--- Top 5 Processes by Memory ---"
ps aux --sort=-%mem | head -6
Best Practices
- Always quote variables:
"$var"prevents word splitting and glob expansion. - Use
set -euo pipefailat the top of scripts to catch errors early. - Use
shellcheckto lint your scripts for common mistakes. - Use
localfor function variables to avoid polluting the global scope. - Always check return codes for critical operations.