Table of Contents
- What is Bash Scripting?
- Setting Up Your Environment
- Your First Bash Script
- Variables in Bash
- Control Structures: Conditionals and Loops
- Handling Inputs: Command-Line Arguments
- Functions for Reusability
- Real-World Automation Examples
- Best Practices for Bash Scripts
- Troubleshooting Common Issues
- References
1. What is Bash Scripting?
Bash scripting is the process of writing a sequence of commands in a text file (called a “script”) that the Bash shell can execute. Unlike compiled languages (e.g., C++), Bash scripts are interpreted—meaning the shell reads and runs commands line by line without needing to be compiled first.
Why Bash?
- Ubiquitous: Preinstalled on Linux, macOS, and WSL. No extra setup required.
- Simplicity: Uses familiar command-line syntax (e.g.,
ls,cp,grep). - Powerful: Supports variables, loops, conditionals, and integration with other tools (e.g.,
awk,sed,git).
2. Setting Up Your Environment
Before writing scripts, ensure your environment is ready:
Prerequisites
- A Bash-compatible terminal:
- Linux: Default terminal (e.g., GNOME Terminal).
- macOS: Terminal or iTerm2.
- Windows: WSL (install via Microsoft Store) or Git Bash.
- A text editor: Use VS Code, Vim, Nano, or Sublime Text.
Verify Bash Installation
Check if Bash is installed by running:
bash --version
You should see output like GNU bash, version 5.1.16(1)-release.
3. Your First Bash Script
Let’s start with a simple “Hello World” script to understand the basics.
Step 1: Create the Script File
Open your text editor and create a new file named hello_world.sh. The .sh extension is conventional for Bash scripts (though not required).
Step 2: Add the Shebang Line
Every Bash script should start with a shebang line (#!), which tells the system which interpreter to use. For Bash, this is:
#!/bin/bash
Step 3: Add Commands
Below the shebang, add a command to print text to the terminal using echo:
#!/bin/bash
# This is a comment (ignored by the shell)
echo "Hello, World!" # Print a message
Step 4: Make the Script Executable
By default, the file won’t be executable. Use chmod to grant execute permissions:
chmod +x hello_world.sh
Step 5: Run the Script
Execute the script with:
./hello_world.sh
Output:
Hello, World!
Pro Tip: To run scripts from any directory, add their location to your $PATH (e.g., export PATH="$HOME/scripts:$PATH" in ~/.bashrc).
4. Variables in Bash
Variables store data for reuse. They make scripts dynamic and easier to maintain.
Defining Variables
Use VAR_NAME=value (no spaces around =):
name="Alice"
age=30
Accessing Variables
Prefix the variable name with $:
echo "Name: $name" # Output: Name: Alice
echo "Age: $age" # Output: Age: 30
Environment Variables
Bash provides built-in variables for system info (e.g., $HOME for your home directory, $PATH for executable paths):
echo "Home: $HOME" # Output: Home: /home/alice
echo "Path: $PATH" # Output: Path: /usr/local/sbin:/usr/local/bin:...
User Input with read
Use read to capture input from the user:
#!/bin/bash
echo "Enter your name:"
read username # Stores input in $username
echo "Hello, $username!"
Run it:
./greet.sh
Enter your name: Bob
Hello, Bob!
5. Control Structures: Conditionals and Loops
Bash supports logic to make scripts decision-driven and iterative.
Conditionals: if Statements
Use if to run commands only if a condition is true.
Syntax:
if [ condition ]; then
# Commands if true
elif [ another_condition ]; then
# Commands if first false, second true
else
# Commands if all false
fi
Common Conditions:
- File tests:
-f file: True iffileexists and is a regular file.-d dir: True ifdirexists and is a directory.-e path: True ifpathexists.
- String comparisons:
string1 == string2: True if equal.string1 != string2: True if not equal.
- Numeric comparisons:
num1 -eq num2: Equal.num1 -ne num2: Not equal.num1 -lt num2: Less than.num1 -gt num2: Greater than.
Example: Check if a File Exists
#!/bin/bash
file="data.txt"
if [ -f "$file" ]; then
echo "$file exists."
else
echo "$file does NOT exist."
fi
Loops: for and while
for Loop: Iterate Over a List
#!/bin/bash
# Loop over fruits
fruits=("apple" "banana" "cherry")
for fruit in "${fruits[@]}"; do
echo "I like $fruit"
done
Output:
I like apple
I like banana
I like cherry
while Loop: Run Until a Condition Fails
#!/bin/bash
count=1
while [ $count -le 5 ]; do
echo "Count: $count"
count=$((count + 1)) # Increment count
done
Output:
Count: 1
Count: 2
Count: 3
Count: 4
Count: 5
6. Handling Inputs: Command-Line Arguments
Scripts often need input when run (e.g., a filename or directory). Use command-line arguments for this.
Key Variables for Arguments
$1,$2, …: The first, second, etc., argument.$@: All arguments as a list.$#: Number of arguments.$0: The script’s filename.
Example: Backup Script with Arguments
#!/bin/bash
# Usage: ./backup.sh <source_dir> <dest_dir>
source_dir="$1"
dest_dir="$2"
# Check if arguments are provided
if [ $# -ne 2 ]; then
echo "Usage: $0 <source_dir> <dest_dir>"
exit 1 # Exit with error code 1
fi
# Check if source exists
if [ ! -d "$source_dir" ]; then
echo "Error: Source directory $source_dir does not exist."
exit 1
fi
# Create destination if it doesn't exist
mkdir -p "$dest_dir"
# Copy files (add -v for verbose)
cp -r "$source_dir"/* "$dest_dir/"
echo "Backup from $source_dir to $dest_dir completed!"
Run it:
./backup.sh ~/Documents ~/Backups/Docs
Backup from /home/alice/Documents to /home/alice/Backups/Docs completed!
7. Functions for Reusability
Functions let you group commands into reusable blocks, making scripts cleaner and easier to maintain.
Syntax:
function_name() {
# Commands here
# Access parameters with $1, $2, etc.
}
Example: A File Backup Function
#!/bin/bash
# Function to backup a file
backup_file() {
local source="$1" # Local variable (only in function)
local dest="$2"
if [ ! -f "$source" ]; then
echo "Error: $source not found."
return 1 # Return error code
fi
cp "$source" "$dest"
echo "Backed up $source to $dest"
}
# Use the function
backup_file "notes.txt" "notes_backup.txt"
backup_file "missing.txt" "nowhere.txt" # Will error
Output:
Backed up notes.txt to notes_backup.txt
Error: missing.txt not found.
8. Real-World Automation Examples
Let’s apply what we’ve learned to solve common problems.
Example 1: Log Cleanup Script
Delete log files older than 7 days to free up space.
#!/bin/bash
LOG_DIR="/var/log/myapp"
DAYS=7
# Check if log directory exists
if [ ! -d "$LOG_DIR" ]; then
echo "Error: $LOG_DIR does not exist."
exit 1
fi
# Delete files older than 7 days (add -v for verbose)
find "$LOG_DIR" -name "*.log" -type f -mtime +$DAYS -delete
echo "Deleted logs older than $DAYS days in $LOG_DIR."
How to Use:
- Save as
clean_logs.sh. - Make executable:
chmod +x clean_logs.sh. - Run with sudo if logs require permissions:
sudo ./clean_logs.sh.
Example 2: System Monitoring Script
Check CPU usage, disk space, and memory, and alert if thresholds are exceeded.
#!/bin/bash
# Thresholds (adjust as needed)
CPU_THRESHOLD=80 # %
DISK_THRESHOLD=85 # %
MEM_THRESHOLD=80 # %
# Check CPU usage (using top for single value)
cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2 + $4}')
cpu_usage=${cpu_usage%.*} # Remove decimal
# Check disk space (root partition)
disk_usage=$(df -h / | awk 'NR==2 {print $5}' | sed 's/%//')
# Check memory usage (used %)
mem_usage=$(free | awk '/Mem/ {print $3/$2 * 100}' | awk '{print int($1)}')
# Alert if thresholds exceeded
if [ "$cpu_usage" -gt "$CPU_THRESHOLD" ]; then
echo "ALERT: CPU usage is $cpu_usage% (threshold: $CPU_THRESHOLD%)"
fi
if [ "$disk_usage" -gt "$DISK_THRESHOLD" ]; then
echo "ALERT: Disk usage is $disk_usage% (threshold: $DISK_THRESHOLD%)"
fi
if [ "$mem_usage" -gt "$MEM_THRESHOLD" ]; then
echo "ALERT: Memory usage is $mem_usage% (threshold: $MEM_THRESHOLD%)"
fi
How to Use:
- Run manually:
./monitor_system.sh. - Automate with
cron(see Bonus: Scheduling with Cron).
Example 3: Batch Rename Files
Add a prefix (e.g., “vacation_”) to all .jpg files in a directory.
#!/bin/bash
# Usage: ./rename_jpgs.sh <prefix>
prefix="$1"
if [ $# -ne 1 ]; then
echo "Usage: $0 <prefix>"
exit 1
fi
count=1
for file in *.jpg; do
if [ -f "$file" ]; then # Ensure it's a file
new_name="${prefix}_${count}.jpg"
mv "$file" "$new_name"
echo "Renamed $file to $new_name"
count=$((count + 1))
fi
done
How to Use:
./rename_jpgs.sh vacation
Renamed img1.jpg to vacation_1.jpg
Renamed img2.jpg to vacation_2.jpg
Bonus: Scheduling with Cron
To run scripts automatically (e.g., daily), use cron, a time-based job scheduler.
-
Open the crontab editor:
crontab -e -
Add a line to schedule your script. For example, run
clean_logs.shdaily at 2 AM:0 2 * * * /path/to/clean_logs.shCron Syntax:
min hour day month weekday command*= every (e.g.,* * * * *= every minute).
9. Best Practices for Bash Scripts
Follow these tips to write reliable, maintainable scripts:
- Use the Shebang Line: Always start with
#!/bin/bash(not#!/bin/shfor portability). - Comment Liberally: Explain why (not just what) the code does.
- Quote Variables: Use
"$VAR"instead of$VARto handle spaces in filenames (e.g.,"My File.txt"). - Check for Errors: Use
set -eto exit on errors, orset -euto exit on errors/unset variables:#!/bin/bash set -eu # Strict mode: exit on error or undefined variable - Test Thoroughly: Run scripts with
bash -n script.shto check for syntax errors before execution. - Use Absolute Paths: Avoid relative paths (e.g.,
/home/user/scriptsinstead of./scripts). - Make Scripts Idempotent: Ensure running the script multiple times has the same effect (e.g.,
mkdir -pinstead ofmkdir).
10. Troubleshooting Common Issues
- “Permission denied”: Use
chmod +x script.shto make it executable. - Syntax errors: Check for missing
then,fi, ordone; ensure spaces after[and before]in conditions (e.g.,[ -f "$file" ]not[-f"$file"]). - Variables not updating: Ensure no spaces around
=when defining variables (e.g.,name="Alice"notname = "Alice"). - Debugging: Add
set -xat the top of the script to print commands as they run (remove when done).
11. References
- GNU Bash Manual
- Bash Scripting Tutorial (ShellScript.net)
- tldr pages: Quick command references (e.g.,
tldr find). - Stack Overflow Bash Tag: Community Q&A.
Conclusion
Bash scripting is a superpower for automating repetitive tasks. With variables, conditionals, loops, and functions, you can build scripts to handle backups, log cleanup, system monitoring, and more. Start small, test often, and gradually tackle more complex workflows. Your future self (and your productivity) will thank you!
Happy scripting! 🚀