Table of Contents
- What is Bash Scripting?
- Setting Up Your First Bash Script
- 2.1 The Shebang Line
- 2.2 Making Scripts Executable
- 2.3 Your First Script: “Hello World” (and Beyond)
- Core Concepts for Effective Scripting
- 3.1 Variables: Storing and Using Data
- 3.2 User Input: Interacting with the User
- 3.3 Conditionals: Making Decisions (if-else)
- 3.4 Loops: Repeating Actions (for, while)
- 3.5 Functions: Reusing Code
- Practical Examples of Task Automation
- 4.1 Automated File Backup
- 4.2 Log Rotation
- 4.3 System Resource Monitoring
- 4.4 Batch File Renaming
- Advanced Tips and Best Practices
- 5.1 Error Handling
- 5.2 Debugging Techniques
- 5.3 Writing Readable Scripts
- 5.4 Portability
- Tools to Enhance Bash Scripting
- 6.1 Scheduling with Cron
- 6.2 Text Processing with sed/awk
- Conclusion
- References
What is Bash Scripting?
Bash scripting is the practice of writing sequences of Bash commands into a text file (called a “script”) that the Bash interpreter can execute as a single program. Instead of typing commands one-by-one in the terminal, you write them once in a script and run the script to automate the entire workflow.
Why Bash?
- Ubiquitous: Bash is preinstalled on all Linux/macOS systems (and available for Windows via WSL). No extra setup required!
- Lightweight: Scripts are plain text files—no compilation needed.
- Powerful: Combine simple commands (e.g.,
ls,cp,grep) with logic (conditionals, loops) to build complex workflows.
Setting Up Your First Bash Script
Let’s start with the basics: creating and running a simple Bash script.
2.1 The Shebang Line
Every Bash script should start with a shebang line, which tells the system which interpreter to use to run the script. For Bash, this is:
#!/bin/bash
This line must be the first line of the script. Without it, the system may default to a different shell (like sh), which lacks some Bash-specific features.
2.2 Making Scripts Executable
Scripts are just text files—you need to mark them as executable to run them directly. Here’s how:
- Create a new file (e.g.,
my_script.sh) with a text editor likenanoorvim. - Add the shebang line and your commands.
- Make it executable with
chmod:chmod +x my_script.sh - Run the script:
./my_script.sh # Runs the script in the current directory # Or: bash my_script.sh # Explicitly uses Bash (useful if permissions aren’t set)
2.3 Your First Script: “Hello World” (and Beyond)
Let’s write a simple script to print a greeting. Open greet.sh and add:
#!/bin/bash
# This is a comment (ignored by Bash)
echo "Hello, World!" # Print "Hello, World!" to the terminal
# Get the current user’s name
username=$(whoami) # Store output of `whoami` in a variable
# Greet the user
echo "Welcome, $username! Today is $(date +%A)." # Use command substitution
How it works:
#!specifies the interpreter.#starts a comment (use these to explain your code!).echoprints text to the terminal.$(command)is command substitution: runscommandand inserts its output into the script. Here,$(whoami)gets the username, and$(date +%A)gets the current day of the week.
Run it:
chmod +x greet.sh
./greet.sh
Output:
Hello, World!
Welcome, alice! Today is Wednesday.
Core Concepts for Effective Scripting
To build useful scripts, you need to master these foundational concepts.
3.1 Variables: Storing and Using Data
Variables let you store data (text, numbers, command output) for later use.
Syntax:
- Declare:
variable_name="value"(no spaces around=!). - Use:
$variable_nameor${variable_name}(curly braces help avoid ambiguity).
Example:
#!/bin/bash
# Assign variables
greeting="Hello"
name="Bob"
age=30
# Use variables
echo "$greeting, $name! You are $age years old."
# Command substitution (store output of a command)
current_dir=$(pwd)
echo "You are in: $current_dir"
Output:
Hello, Bob! You are 30 years old.
You are in: /home/alice/scripts
3.2 User Input: Interacting with the User
Use the read command to get input from the user.
Syntax:
read -p "Prompt message: " variable_name # -p adds a prompt
Example:
#!/bin/bash
read -p "Enter your name: " name
read -p "Enter your favorite color: " color
echo "Hello, $name! Your favorite color is $color."
Run it:
./input.sh
Enter your name: Charlie
Enter your favorite color: Blue
Hello, Charlie! Your favorite color is Blue.
3.3 Conditionals: Making Decisions (if-else)
Conditionals let your script execute different commands based on whether a condition is true or false. Use if, elif (else if), and fi (ends the if block).
Syntax:
if [ condition ]; then
# Commands to run if condition is true
elif [ another_condition ]; then
# Commands if first condition is false, second is true
else
# Commands if all conditions are false
fi
Common Conditions:
-f "file.txt": True iffile.txtexists and is a regular file.-d "dir": True ifdirexists and is a directory.$var -eq 5: True ifvaris equal to 5 (numeric comparison)."$var" == "text": True ifvarequals “text” (string comparison).-z "$var": True ifvaris empty.
Example: Check if a file exists
#!/bin/bash
read -p "Enter a filename: " filename
if [ -f "$filename" ]; then
echo "$filename exists. Contents:"
cat "$filename" # Print file contents
elif [ -d "$filename" ]; then
echo "$filename is a directory, not a file."
else
echo "$filename does NOT exist."
fi
3.4 Loops: Repeating Actions (for, while)
Loops automate repetitive tasks, like processing multiple files or lines of text.
For Loops: Iterate Over a List
Syntax:
for item in list; do
# Commands to run for each item
done
Example: Process all .txt files
#!/bin/bash
echo "Processing .txt files in current directory:"
for file in *.txt; do
echo "- $file has $(wc -l < "$file") lines." # Count lines in each file
done
While Loops: Repeat Until a Condition Fails
Syntax:
while [ condition ]; do
# Commands to repeat
done
Example: Countdown from 5
#!/bin/bash
count=5
while [ $count -gt 0 ]; do
echo $count
count=$((count - 1)) # Decrement count by 1
sleep 1 # Wait 1 second
done
echo "Blast off!"
3.5 Functions: Reusing Code
Functions let you group commands into reusable blocks, making scripts cleaner and easier to maintain.
Syntax:
function_name() {
# Commands here
# Use $1, $2, etc., to access parameters passed to the function
}
Example: Greeting Function
#!/bin/bash
# Define function
greet() {
local name="$1" # Local variable (only exists inside the function)
echo "Hello, $name! 🎉"
}
# Call the function with different names
greet "Alice"
greet "Bob"
greet "Charlie"
Output:
Hello, Alice! 🎉
Hello, Bob! 🎉
Hello, Charlie! 🎉
Practical Examples of Task Automation
Now that you know the basics, let’s build scripts to solve real-world problems.
4.1 Automated File Backup
A common task: backing up a directory (e.g., Documents) to an external drive or cloud folder. This script will create a timestamped backup zip file.
Script: backup.sh
#!/bin/bash
# Configuration
source_dir="/home/alice/Documents" # Directory to back up
dest_dir="/mnt/external_drive/backups" # Where to store backups
timestamp=$(date +%Y%m%d_%H%M%S) # e.g., 20240520_143022
backup_file="$dest_dir/documents_backup_$timestamp.zip"
# Check if source and destination exist
if [ ! -d "$source_dir" ]; then
echo "Error: Source directory $source_dir does not exist."
exit 1 # Exit with error code 1
fi
if [ ! -d "$dest_dir" ]; then
echo "Error: Destination directory $dest_dir does not exist."
exit 1
fi
# Create backup
echo "Creating backup: $backup_file"
zip -r "$backup_file" "$source_dir" # -r = recursive (include subfolders)
# Check if backup succeeded
if [ $? -eq 0 ]; then # $? = exit code of last command (0 = success)
echo "Backup completed successfully!"
else
echo "Backup FAILED!"
exit 1
fi
4.2 Log Rotation
Logs (e.g., /var/log/syslog) grow indefinitely. This script compresses old logs, deletes very old ones, and keeps only the last 5 backups.
Script: rotate_logs.sh
#!/bin/bash
log_file="/var/log/syslog" # Log file to rotate
backup_dir="/var/log/old_logs" # Store compressed logs here
max_backups=5 # Keep only last 5 backups
# Create backup dir if it doesn’t exist
mkdir -p "$backup_dir"
# Compress the current log file (add timestamp)
timestamp=$(date +%Y%m%d)
compressed_log="$backup_dir/syslog_$timestamp.gz"
gzip -c "$log_file" > "$compressed_log" # -c = write to stdout (redirect to file)
# Truncate the original log file (so new logs can be written)
> "$log_file" # Overwrites the file with empty
# Delete oldest backups if we have more than max_backups
num_backups=$(ls -1 "$backup_dir"/syslog_*.gz | wc -l)
if [ $num_backups -gt $max_backups ]; then
echo "Deleting oldest backups (keeping last $max_backups)..."
# List backups by name (sorted), take the oldest ones, delete them
ls -1tr "$backup_dir"/syslog_*.gz | head -n -$max_backups | xargs rm -f
fi
echo "Log rotation completed. Compressed log: $compressed_log"
4.3 System Resource Monitoring
This script checks CPU usage, disk space, and memory, and sends an alert if any exceed thresholds (e.g., disk > 90% full).
Script: monitor_system.sh
#!/bin/bash
# Thresholds (adjust as needed)
cpu_threshold=80 # % CPU usage
disk_threshold=90 # % disk usage (root partition)
mem_threshold=85 # % memory usage
alert_email="[email protected]" # Send alerts here
# Check CPU usage (using top, extract idle %, subtract from 100)
cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print 100 - $8 "%"}' | cut -d% -f1)
# Check disk usage (root partition)
disk_usage=$(df -h / | awk 'NR==2 {print $5}' | cut -d% -f1)
# Check memory usage (using free, extract used %)
mem_usage=$(free | awk '/Mem/ {printf "%.0f", $3/$2 * 100}')
# Build alert message
alert_msg=""
if [ $cpu_usage -gt $cpu_threshold ]; then
alert_msg+="⚠️ High CPU usage: $cpu_usage% (threshold: $cpu_threshold%)\n"
fi
if [ $disk_usage -gt $disk_threshold ]; then
alert_msg+="⚠️ High disk usage: $disk_usage% (threshold: $disk_threshold%)\n"
fi
if [ $mem_usage -gt $mem_threshold ]; then
alert_msg+="⚠️ High memory usage: $mem_usage% (threshold: $mem_threshold%)\n"
fi
# Send alert if needed
if [ -n "$alert_msg" ]; then # -n = non-empty string
echo -e "System Alert:\n$alert_msg" | mail -s "⚠️ High Resource Usage" "$alert_email"
echo "Alert sent to $alert_email"
else
echo "All resources are within safe thresholds."
fi
4.4 Batch File Renaming
Rename all .jpg files in a folder to vacation_001.jpg, vacation_002.jpg, etc.
Script: rename_photos.sh
#!/bin/bash
read -p "Enter directory with .jpg files: " dir
prefix="vacation" # New filename prefix
counter=1 # Start numbering at 1
# Check if directory exists
if [ ! -d "$dir" ]; then
echo "Error: Directory $dir does not exist."
exit 1
fi
# Move to the directory (to avoid full paths in filenames)
cd "$dir" || exit 1
# Rename .jpg files (sorted alphabetically)
for file in *.jpg; do
# Skip if no .jpg files exist
if [ ! -f "$file" ]; then
echo "No .jpg files found in $dir."
exit 0
fi
# Pad counter with leading zeros (e.g., 1 → 001)
new_name="${prefix}_$(printf "%03d" $counter).jpg"
# Rename the file
mv -v "$file" "$new_name" # -v = verbose (show rename action)
counter=$((counter + 1))
done
echo "Renamed $((counter - 1)) files."
Advanced Tips and Best Practices
5.1 Error Handling
set -e: Exit the script immediately if any command fails (avoids running broken code).set -u: Treat undefined variables as errors (prevents silent bugs).trap: Run cleanup commands (e.g., delete temp files) if the script is interrupted (e.g.,Ctrl+C).
Example:
#!/bin/bash
set -euo pipefail # -e: exit on error, -u: error on undefined var, -o pipefail: exit if any command in a pipe fails
# Cleanup function
cleanup() {
echo "Cleaning up temp files..."
rm -f /tmp/my_temp_file.txt
}
trap cleanup EXIT # Run cleanup when script exits (success or failure)
# Rest of script...
5.2 Debugging Techniques
bash -x script.sh: Run the script in debug mode (prints each command before executing it).echo "Debug: variable=$var": Add debug messages to track variables.set -x: Enable debug mode inside the script (useset +xto disable).
5.3 Writing Readable Scripts
- Comments: Explain why (not just what) the code does.
- Meaningful names: Use
backup_dirinstead ofb. - Indentation: Use 2–4 spaces to group code blocks (e.g., inside
if/for).
5.4 Portability
- Use
#!/bin/shinstead of#!/bin/bashif targeting systems without Bash (e.g., BSD). - Avoid Bash-specific features (e.g., arrays,
[[ ]]conditionals) if writing POSIX-compliant scripts.
Tools to Enhance Bash Scripting
5.1 Scheduling with Cron
Use cron to run scripts automatically (e.g., daily backups at 2 AM).
How to set up a cron job:
- Run
crontab -eto edit your cron table. - Add a line with the schedule and script path:
# Format: minute hour day month weekday command 0 2 * * * /home/alice/scripts/backup.sh # Run daily at 2:00 AM - Save and exit. Cron will run the script automatically.
5.2 Text Processing with sed/awk
sed: Edit text in-place (e.g., replace “old” with “new” in a file:sed -i 's/old/new/g' file.txt).awk: Process structured text (e.g., sum the 3rd column of a CSV:awk -F ',' '{sum += $3} END {print sum}' data.csv).
Conclusion
Bash scripting is a cornerstone of Linux automation, empowering you to turn tedious tasks into one-click (or scheduled) workflows. By mastering variables, conditionals, loops, and functions, and following best practices like error handling and readability, you can build robust scripts to handle backups, monitoring, log rotation, and more.
Start small (e.g., a script to rename photos), then tackle bigger projects. With cron for scheduling and tools like sed/awk for text processing, the possibilities are endless.