Table of Contents
- What is Bash Scripting?
- Getting Started: Your First Bash Script
- Variables and Data Types
- Control Structures: Making Decisions and Looping
- Functions: Reusable Code Blocks
- Command-Line Arguments: Making Scripts Dynamic
- Practical Examples: Real-World Automation
- Debugging Bash Scripts
- Best Practices for Robust Scripts
- Conclusion
- References
What is Bash Scripting?
Bash scripting is the art of writing sequences of commands in a text file (called a “script”) that the Bash shell can execute. Think of it as a “recipe” for the shell: it tells the system what to do, when to do it, and how to handle errors.
Why Bash Scripting?
- Automation: Eliminate repetitive tasks (e.g., backups, log rotation, file renaming).
- Consistency: Ensure tasks are executed the same way every time.
- Accessibility: No need for complex programming languages—uses familiar Linux commands.
- Flexibility: Integrate with other tools (e.g.,
grep,awk,sed,cronfor scheduling).
Getting Started: Your First Bash Script
Let’s create a simple “Hello World” script to understand the basics.
Step 1: Create the Script File
Open a text editor (e.g., nano, vim) and create a file named hello_world.sh:
nano hello_world.sh
Step 2: Add the Shebang Line
The first line of a bash script is the shebang (#!), which tells the system which interpreter to use. For Bash, this is:
#!/bin/bash
Step 3: Add Commands
Below the shebang, add the command to print “Hello, World!”:
#!/bin/bash
echo "Hello, World!"
Step 4: Make the Script Executable
By default, the script is not executable. Use chmod to set execute permissions:
chmod +x hello_world.sh
Step 5: Run the Script
Execute the script with ./ (to specify the current directory):
./hello_world.sh
Output:
Hello, World!
Congratulations! You’ve written and run your first bash script.
Variables and Data Types
Variables store data for reuse in scripts. Bash is loosely typed, so you don’t need to declare variable types (e.g., string, integer).
Declaring Variables
Assign values to variables without spaces around the = sign:
name="Alice"
age=30
Accessing Variables
Use $ to access a variable’s value:
echo "Name: $name" # Output: Name: Alice
echo "Age: $age" # Output: Age: 30
Command Substitution
Capture the output of a command into a variable using $(command) or backticks `command`:
current_date=$(date +%Y-%m-%d) # Stores "2024-05-20" (example)
echo "Today is $current_date"
Arrays
Bash supports arrays for storing multiple values:
fruits=("apple" "banana" "cherry")
# Access an element (indexes start at 0)
echo "First fruit: ${fruits[0]}" # Output: First fruit: apple
# Loop through all elements
for fruit in "${fruits[@]}"; do
echo "Fruit: $fruit"
done
Control Structures: Making Decisions and Looping
Control structures let you add logic to scripts, such as conditionals (if-else) and loops (for, while).
If-Else Statements
Check conditions and execute commands based on the result.
Syntax:
if [ condition ]; then
# Commands 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
Example: Check if a file exists:
file="example.txt"
if [ -f "$file" ]; then
echo "$file exists."
elif [ -d "$file" ]; then
echo "$file is a directory."
else
echo "$file does not exist."
fi
Common Conditions:
-f file: True iffileis a regular file.-d dir: True ifdiris a directory.-z str: True ifstris empty.$a -eq $b: True if integersaandbare equal.$str1 = $str2: True if stringsstr1andstr2are equal.
For Loops
Iterate over a list of items (e.g., files, numbers).
Syntax:
for item in list; do
# Commands for each item
done
Examples:
- Iterate over files in a directory:
for file in /home/user/documents/*.txt; do echo "Processing: $file" done - Iterate over a range of numbers:
for i in {1..5}; do echo "Count: $i" done
While Loops
Repeat commands as long as a condition is true.
Syntax:
while [ condition ]; do
# Commands
done
Example: Count down from 5:
count=5
while [ $count -gt 0 ]; do
echo $count
count=$((count - 1)) # Decrement count
sleep 1 # Wait 1 second
done
echo "Blast off!"
Functions: Reusable Code Blocks
Functions let you group commands into reusable blocks, making scripts cleaner and easier to maintain.
Defining a Function
greet() {
local name=$1 # Local variable (only visible in the function)
echo "Hello, $name!"
}
Calling a Function
greet "Bob" # Output: Hello, Bob!
Return Values
Bash functions return exit codes (0 for success, non-zero for failure) by default, but you can use command substitution to “return” values:
get_sum() {
local a=$1
local b=$2
echo $((a + b)) # Print the sum (captured via command substitution)
}
sum=$(get_sum 3 5)
echo "Sum: $sum" # Output: Sum: 8
Command-Line Arguments: Making Scripts Dynamic
Scripts often need input from the user. Use command-line arguments to pass values when running the script.
Accessing Arguments
$0: Name of the script.$1, $2, ...: First, second, etc., argument.$#: Number of arguments.$*or$@: All arguments (as a single string or list, respectively).
Example: A script that greets a user:
#!/bin/bash
# Check if an argument is provided
if [ $# -eq 0 ]; then
echo "Usage: $0 <name>"
exit 1 # Exit with error code 1
fi
name=$1
echo "Hello, $name!"
Run it:
./greet.sh "Alice" # Output: Hello, Alice!
Practical Examples: Real-World Automation
Let’s build three useful scripts to apply what we’ve learned.
Example 1: Automated Backup Script
This script backs up a directory to a compressed .tar.gz file with a timestamp.
#!/bin/bash
# Backup script: Backs up a directory to ~/backups with a timestamp.
# Configuration
source_dir="/home/user/documents" # Directory to back up
backup_dir="$HOME/backups" # Where to store backups
timestamp=$(date +%Y%m%d_%H%M%S) # e.g., 20240520_143022
backup_file="$backup_dir/docs_backup_$timestamp.tar.gz"
# Create backup directory if it doesn't exist
mkdir -p "$backup_dir"
# Check if source directory exists
if [ ! -d "$source_dir" ]; then
echo "Error: Source directory $source_dir does not exist."
exit 1
fi
# Perform backup
echo "Backing up $source_dir to $backup_file..."
tar -czf "$backup_file" -C "$(dirname "$source_dir")" "$(basename "$source_dir")"
# Check if backup succeeded
if [ $? -eq 0 ]; then
echo "Backup completed successfully!"
else
echo "Backup failed!"
exit 1
fi
How it works:
tar -czf: Creates a compressed archive (c=create,z=gzip,f=file).-C dir: Changes todirbefore archiving (avoids including full paths).$?: Exit code of the last command (0 = success).
Example 2: Log Cleaner Script
This script deletes log files older than 7 days to free up space.
#!/bin/bash
# Log cleaner: Deletes logs older than 7 days in /var/log.
log_dir="/var/log"
days=7
echo "Cleaning logs older than $days days in $log_dir..."
# Find and delete .log files older than 7 days
find "$log_dir" -name "*.log" -type f -mtime +$days -delete
# Check if find succeeded
if [ $? -eq 0 ]; then
echo "Log cleaning completed."
else
echo "Error: Failed to clean logs."
exit 1
fi
How it works:
find: Searches for files matching criteria.-mtime +$days: Files modified more thandaysago.-delete: Deletes found files (use-printfirst to test!).
Example 3: System Info Script
This script displays key system metrics (CPU, memory, disk usage).
#!/bin/bash
# System info: Displays CPU, memory, and disk usage.
echo "=== SYSTEM INFORMATION ==="
echo "Date: $(date)"
echo "Hostname: $(hostname)"
echo ""
echo "=== CPU USAGE ==="
top -bn1 | grep "Cpu(s)" | awk '{print "Usage: " $2 "%"}'
echo ""
echo "=== MEMORY USAGE ==="
free -h | awk '/Mem:/ {print "Total: " $2 ", Used: " $3 ", Free: " $4}'
echo ""
echo "=== DISK USAGE ==="
df -h / | awk 'NR==2 {print "Root: " $5 " used (" $3 "/" $2 ")"}'
How it works:
top -bn1: Runstopin batch mode once.free -h: Shows memory usage in human-readable format.df -h /: Shows disk usage for the root partition.
Debugging Bash Scripts
Debugging is critical for fixing errors. Here are tools to help:
1. Enable Debug Mode
Add -x to the shebang or run the script with bash -x to print each command before execution:
#!/bin/bash -x # Debug mode enabled
Or:
bash -x script.sh
2. Check Exit Codes
Use echo $? after a command to see its exit code (0 = success, non-zero = failure).
3. Use shellcheck
shellcheck is a linter that flags errors and bad practices. Install it with sudo apt install shellcheck (Debian/Ubuntu) or brew install shellcheck (macOS), then run:
shellcheck script.sh
Best Practices for Robust Scripts
Follow these guidelines to write maintainable, error-resistant scripts:
- Add Comments: Explain why (not just what) the code does.
- Use Meaningful Names: Variables like
backup_dirare clearer thanbd. - Quote Variables: Prevent word splitting (e.g.,
"$filename"instead of$filename). - Handle Errors: Use
set -eto exit on any error, andset -uto catch undefined variables:# At the top of the script set -euo pipefail # Exit on error, undefined var, or pipeline failure - Test Thoroughly: Run in a non-production environment first.
- Limit Scope: Keep scripts focused on one task (e.g., backup or log cleaning, not both).
Conclusion
Bash scripting is a superpower for Linux users, turning tedious tasks into one-click automations. From simple backups to complex system monitoring, the ability to write bash scripts unlocks a new level of control over your system.
Start small—automate one task this week (e.g., a file organizer or backup script). As you practice, you’ll build confidence to tackle more advanced projects. Remember: the best scripts are simple, well-documented, and solve real problems.