Linux Shell Scripting
Shell scripting is a powerful way to automate tasks, combine commands, and create custom tools in Linux. This guide covers bash scripting fundamentals, syntax, and practical examples for automation.
Overview
Shell scripting involves several key concepts:
- Shell - Command interpreter (bash, sh, zsh, etc.)
- Script - Text file containing shell commands
- Shebang - First line specifying the interpreter (#!/bin/bash)
- Variables - Store and manipulate data
- Control Structures - Loops, conditionals, and functions
- I/O Redirection - Handle input and output streams
Basic Script Structure
Simple script example
#!/bin/bash
# This is a comment
# Simple hello world script
echo "Hello, World!"
echo "Current date: $(date)"
echo "Current user: $USER"
echo "Current directory: $(pwd)"
Basic script structure with shebang, comments, and commands
Creating and running scripts
# Create script file
nano hello.sh
# Make executable
chmod +x hello.sh
# Run script
./hello.sh
# Alternative ways to run
bash hello.sh
sh hello.sh
Steps to create, make executable, and run shell scripts
Variables
Variable declaration and usage
#!/bin/bash
# Variable examples
# String variables
name="John Doe"
greeting="Hello"
# Numeric variables
age=30
count=0
# Using variables
echo "$greeting, $name!"
echo "Age: $age"
echo "Full message: ${greeting}, ${name}! You are ${age} years old."
# Command substitution
current_date=$(date)
file_count=$(ls | wc -l)
echo "Today is: $current_date"
echo "Files in directory: $file_count"
Variable declaration, assignment, and usage in scripts
Special variables
#!/bin/bash
# Special variables
echo "Script name: $0"
echo "First argument: $1"
echo "Second argument: $2"
echo "All arguments: $@"
echo "Number of arguments: $#"
echo "Process ID: $$"
echo "Exit status of last command: $?"
echo "Current user: $USER"
echo "Home directory: $HOME"
Built-in variables available in shell scripts
Environment variables
# Set environment variable
export MY_VAR="Hello World"
# Use in script
echo $MY_VAR
# Check if variable is set
if [ -z "$MY_VAR" ]; then
echo "MY_VAR is not set"
else
echo "MY_VAR is set to: $MY_VAR"
fi
Working with environment variables in scripts
Control Structures
If statements
#!/bin/bash
# If statement examples
age=25
# Simple if
if [ $age -gt 18 ]; then
echo "You are an adult"
fi
# If-else
if [ $age -ge 21 ]; then
echo "You can drink alcohol"
else
echo "You cannot drink alcohol"
fi
# If-elif-else
if [ $age -lt 13 ]; then
echo "You are a child"
elif [ $age -lt 20 ]; then
echo "You are a teenager"
else
echo "You are an adult"
fi
# String comparison
name="Alice"
if [ "$name" = "Alice" ]; then
echo "Hello Alice!"
fi
# File tests
if [ -f "/etc/passwd" ]; then
echo "Password file exists"
fi
Conditional statements and comparisons
Loops
#!/bin/bash
# Loop examples
# For loop with range
for i in {1..5}; do
echo "Number: $i"
done
# For loop with list
for fruit in apple banana orange; do
echo "Fruit: $fruit"
done
# For loop with files
for file in *.txt; do
echo "Processing: $file"
done
# While loop
count=1
while [ $count -le 5 ]; do
echo "Count: $count"
count=$((count + 1))
done
# Until loop
num=1
until [ $num -gt 3 ]; do
echo "Number: $num"
num=$((num + 1))
done
Different types of loops for iteration
Case statements
#!/bin/bash
# Case statement example
echo "Enter your choice (1-3):"
read choice
case $choice in
1)
echo "You chose option 1"
;;
2)
echo "You chose option 2"
;;
3)
echo "You chose option 3"
;;
*)
echo "Invalid choice"
;;
esac
# Case with patterns
echo "Enter a file extension:"
read ext
case $ext in
txt|doc)
echo "Text document"
;;
jpg|png|gif)
echo "Image file"
;;
mp3|wav)
echo "Audio file"
;;
*)
echo "Unknown file type"
;;
esac
Case statements for multiple condition handling
Functions
Function definition and usage
#!/bin/bash
# Function examples
# Simple function
greet() {
echo "Hello from function!"
}
# Function with parameters
greet_user() {
local name=$1
local age=$2
echo "Hello $name, you are $age years old"
}
# Function with return value
add_numbers() {
local num1=$1
local num2=$2
local result=$((num1 + num2))
echo $result
}
# Function that returns status
check_file() {
local filename=$1
if [ -f "$filename" ]; then
return 0 # Success
else
return 1 # Failure
fi
}
# Using functions
greet
greet_user "Alice" 25
sum=$(add_numbers 10 20)
echo "Sum: $sum"
if check_file "/etc/passwd"; then
echo "File exists"
else
echo "File does not exist"
fi
Creating and using functions in shell scripts
Operators and Comparisons
| Type |
Operator |
Description |
Example |
| Numeric |
-eq |
Equal to |
[ $a -eq $b ] |
-ne |
Not equal to |
[ $a -ne $b ] |
-gt |
Greater than |
[ $a -gt $b ] |
-lt |
Less than |
[ $a -lt $b ] |
-ge |
Greater than or equal |
[ $a -ge $b ] |
-le |
Less than or equal |
[ $a -le $b ] |
| String |
= |
Equal to |
[ "$a" = "$b" ] |
!= |
Not equal to |
[ "$a" != "$b" ] |
-z |
String is empty |
[ -z "$string" ] |
| File |
-f |
File exists and is regular file |
[ -f "$file" ] |
-d |
Directory exists |
[ -d "$dir" ] |
-r |
File is readable |
[ -r "$file" ] |
-w |
File is writable |
[ -w "$file" ] |
Input and Output
Reading user input
#!/bin/bash
# Input examples
# Simple input
echo "Enter your name:"
read name
echo "Hello, $name!"
# Input with prompt
read -p "Enter your age: " age
echo "You are $age years old"
# Silent input (for passwords)
read -s -p "Enter password: " password
echo
echo "Password entered"
# Reading multiple values
echo "Enter first and last name:"
read first last
echo "Hello, $first $last!"
# Reading from file
while read line; do
echo "Line: $line"
done < input.txt
Various ways to read user input and file content
Output and redirection
#!/bin/bash
# Output examples
# Standard output
echo "This goes to stdout"
# Error output
echo "This is an error" >&2
# Redirect to file
echo "This goes to file" > output.txt
echo "This appends to file" >> output.txt
# Redirect both stdout and stderr
command > output.txt 2>&1
# Pipe to other commands
echo "hello world" | tr '[:lower:]' '[:upper:]'
# Here document
cat << EOF
This is a multi-line
text block that will
be processed by cat
EOF
Output redirection and piping techniques
Error Handling
Exit codes and error checking
#!/bin/bash
# Error handling examples
# Check command success
if cp source.txt destination.txt; then
echo "File copied successfully"
else
echo "Failed to copy file"
exit 1
fi
# Check exit code explicitly
cp source.txt destination.txt
if [ $? -eq 0 ]; then
echo "Copy successful"
else
echo "Copy failed"
fi
# Set error handling options
set -e # Exit on any error
set -u # Exit on undefined variable
set -o pipefail # Exit on pipe failure
# Trap errors
trap 'echo "Error on line $LINENO"' ERR
# Custom error function
error_exit() {
echo "Error: $1" >&2
exit 1
}
# Usage
[ -f "required_file.txt" ] || error_exit "Required file not found"
Proper error handling and debugging techniques
Practical Script Examples
System backup script
#!/bin/bash
# System backup script
BACKUP_DIR="/backup"
SOURCE_DIR="/home"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="backup_$DATE.tar.gz"
# Create backup directory if it doesn't exist
mkdir -p "$BACKUP_DIR"
# Create backup
echo "Starting backup..."
if tar -czf "$BACKUP_DIR/$BACKUP_FILE" "$SOURCE_DIR"; then
echo "Backup completed: $BACKUP_DIR/$BACKUP_FILE"
# Remove old backups (keep last 7)
find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime +7 -delete
echo "Old backups cleaned up"
else
echo "Backup failed!" >&2
exit 1
fi
Automated backup script with error handling
Log monitoring script
#!/bin/bash
# Log monitoring script
LOG_FILE="/var/log/syslog"
ALERT_EMAIL="
[email protected]"
ERROR_PATTERN="ERROR|CRITICAL|FATAL"
# Check if log file exists
if [ ! -f "$LOG_FILE" ]; then
echo "Log file not found: $
LOG_FILE"
exit 1
fi
# Monitor for errors in last 100 lines
recent_errors=$(tail -100 "$LOG_FILE" | grep -E "$ERROR_PATTERN")
if [ -n "$recent_errors" ]; then
echo "Errors found in log file:"
echo "$recent_errors"
# Send alert email (if mail is configured)
echo "$recent_errors" | mail -s "System Alert: Errors Detected" "$ALERT_EMAIL"
else
echo "No recent errors found"
fi
Log monitoring script with email alerts
System information script
#!/bin/bash
# System information script
echo "=== System Information Report ==="
echo "Generated on: $(date)"
echo
echo "=== System Details ==="
echo "Hostname: $(hostname)"
echo "Uptime: $(uptime)"
echo "Kernel: $(uname -r)"
echo "Architecture: $(uname -m)"
echo
echo "=== CPU Information ==="
grep "model name" /proc/cpuinfo | head -1 | cut -d: -f2
echo "CPU Cores: $(nproc)"
echo
echo "=== Memory Usage ==="
free -h
echo
echo "=== Disk Usage ==="
df -h | grep -E "^/dev"
echo
echo "=== Network Interfaces ==="
ip addr show | grep -E "^[0-9]|inet "
echo
echo "=== Top 5 Processes by Memory ==="
ps aux --sort=-%mem | head -6
Comprehensive system information gathering script
Advanced Scripting Techniques
Arrays
#!/bin/bash
# Array examples
# Declare array
fruits=("apple" "banana" "orange" "grape")
# Access elements
echo "First fruit: ${fruits[0]}"
echo "All fruits: ${fruits[@]}"
echo "Number of fruits: ${#fruits[@]}"
# Loop through array
for fruit in "${fruits[@]}"; do
echo "Fruit: $fruit"
done
# Add element
fruits+=("mango")
# Associative arrays (bash 4+)
declare -A colors
colors[red]="#FF0000"
colors[green]="#00FF00"
colors[blue]="#0000FF"
echo "Red color code: ${colors[red]}"
# Loop through associative array
for color in "${!colors[@]}"; do
echo "$color: ${colors[$color]}"
done
Working with indexed and associative arrays
String manipulation
#!/bin/bash
# String manipulation examples
text="Hello World"
# Length
echo "Length: ${#text}"
# Substring
echo "Substring: ${text:0:5}" # "Hello"
echo "From position: ${text:6}" # "World"
# Replace
echo "Replace: ${text/World/Universe}" # "Hello Universe"
echo "Replace all: ${text//l/L}" # "HeLLo WorLd"
# Case conversion
echo "Uppercase: ${text^^}"
echo "Lowercase: ${text,,}"
# Remove prefix/suffix
filename="document.txt"
echo "Name without extension: ${filename%.*}" # "document"
echo "Extension: ${filename##*.}" # "txt"
path="/home/user/documents/file.txt"
echo "Directory: ${path%/*}" # "/home/user/documents"
echo "Filename: ${path##*/}" # "file.txt"
String manipulation and pattern matching
Command line argument processing
#!/bin/bash
# Command line argument processing
# Simple argument processing
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
echo "Usage: $0 [options]"
echo "Options:"
echo " -h, --help Show help"
echo " -v, --verbose Verbose output"
echo " -f, --file Specify file"
exit 0
;;
-v|--verbose)
VERBOSE=1
shift
;;
-f|--file)
FILE="$2"
shift 2
;;
*)
echo "Unknown option: $1"
exit 1
;;
esac
done
# Use the options
if [[ $VERBOSE -eq 1 ]]; then
echo "Verbose mode enabled"
fi
if [[ -n $FILE ]]; then
echo "Processing file: $FILE"
fi
Processing command line options and arguments
Shell Scripting Best Practices
Code Quality
- Use meaningful variable and function names
- Add comments to explain complex logic
- Use consistent indentation (2 or 4 spaces)
- Quote variables to prevent word splitting:
"$variable"
- Use
set -e to exit on errors
- Use
set -u to catch undefined variables
Security Considerations
- Validate all input parameters
- Use absolute paths for critical commands
- Avoid using
eval with user input
- Set proper file permissions (chmod 755 for scripts)
- Don't store passwords in scripts
- Use
mktemp for temporary files
Common Pitfalls
- Unquoted variables - Can cause word splitting and globbing
- Missing error handling - Scripts should handle failures gracefully
- Hardcoded paths - Use variables for paths that might change
- Not checking prerequisites - Verify required commands exist
- Race conditions - Be careful with file operations in concurrent environments
Debugging Shell Scripts
Debug options
# Run script with debug output
bash -x script.sh
# Enable debugging in script
set -x # Enable debug mode
set +x # Disable debug mode
# Verbose mode
set -v # Print commands as they are read
# Combine options
set -xv
Enable debugging and verbose output for troubleshooting
Debug techniques
#!/bin/bash
# Debugging techniques
# Debug function
debug() {
if [[ $DEBUG -eq 1 ]]; then
echo "DEBUG: $*" >&2
fi
}
# Usage
DEBUG=1
debug "Starting script execution"
# Check variable values
echo "Variable value: '$variable'" >&2
# Add checkpoints
echo "Checkpoint 1: Before file processing" >&2
# Validate assumptions
if [[ ! -f "$input_file" ]]; then
echo "ERROR: Input file not found: $input_file" >&2
exit 1
fi
# Log function entry/exit
function_name() {
echo "Entering function_name with args: $*" >&2
# function logic here
echo "Exiting function_name" >&2
}
Debugging techniques and logging for script troubleshooting
Testing Shell Scripts
Basic testing approach
#!/bin/bash
# Simple test framework
test_count=0
pass_count=0
# Test function
run_test() {
local test_name="$1"
local expected="$2"
local actual="$3"
test_count=$((test_count + 1))
if [[ "$expected" == "$actual" ]]; then
echo "PASS: $test_name"
pass_count=$((pass_count + 1))
else
echo "FAIL: $test_name"
echo " Expected: $expected"
echo " Actual: $actual"
fi
}
# Example tests
source_script="./my_script.sh"
# Test function output
result=$(bash "$source_script" arg1 arg2)
run_test "Basic functionality" "expected_output" "$result"
# Test with different inputs
result=$(bash "$source_script" --help)
run_test "Help option" "Usage:" "${result:0:6}"
# Summary
echo "Tests: $test_count, Passed: $pass_count, Failed: $((test_count - pass_count))"
Simple testing framework for shell scripts