A Beginner's Guide to Shell Scripting in Linux
Shell scripting is one of the most powerful tools in a Linux user's arsenal, enabling automation, system administration, and complex task management through simple text-based commands. Whether you're a system administrator, developer, or Linux enthusiast, mastering bash scripting will significantly enhance your productivity and open doors to advanced system management capabilities.
What is Shell Scripting?
Shell scripting involves writing a series of commands that the shell (command interpreter) can execute automatically. The bash shell (Bourne Again Shell) is the most common shell in Linux distributions and serves as our focus throughout this guide. A shell script is essentially a text file containing commands that would normally be typed interactively at the command line.
The power of shell scripting lies in its ability to: - Automate repetitive tasks - Process files and data in bulk - Manage system configurations - Create custom utilities and tools - Schedule and monitor system processes
Setting Up Your Environment
Before diving into scripting, ensure you have access to a Linux system with bash installed. Most Linux distributions come with bash pre-installed. You can verify your shell by running:
`bash
echo $SHELL
`
To check your bash version:
`bash
bash --version
`
Choose a text editor for writing scripts. Popular options include: - nano: Beginner-friendly with on-screen help - vim: Powerful but with a steeper learning curve - gedit: Graphical editor for desktop environments - VS Code: Modern editor with excellent syntax highlighting
Your First Shell Script
Let's create a simple "Hello World" script to understand the basic structure:
`bash
#!/bin/bash
This is a comment
echo "Hello, World!" echo "Welcome to shell scripting!"`Save this as hello.sh and make it executable:
`bash
chmod +x hello.sh
`
Run the script:
`bash
./hello.sh
`
Understanding the Shebang
The first line #!/bin/bash is called a shebang (or hashbang). It tells the system which interpreter to use for executing the script. Always include this line at the beginning of your bash scripts.
Working with Variables
Variables are fundamental to shell scripting, allowing you to store and manipulate data throughout your scripts.
Declaring and Using Variables
`bash
#!/bin/bash
Variable declaration
name="John Doe" age=25 current_date=$(date)Using variables
echo "Name: $name" echo "Age: $age" echo "Today is: $current_date"`Variable Rules and Best Practices
1. No spaces around the equals sign when assigning values 2. Use quotes for strings containing spaces 3. Use descriptive names for better readability 4. Use uppercase for environment variables and constants
`bash
#!/bin/bash
Good practices
USER_NAME="admin" MAX_ATTEMPTS=3 LOG_FILE="/var/log/myapp.log"Accessing variables
echo "User: ${USER_NAME}" echo "Maximum attempts: ${MAX_ATTEMPTS}"`Command Substitution
Capture command output in variables using two methods:
`bash
#!/bin/bash
Method 1: Using backticks (older syntax)
current_user=whoamiMethod 2: Using $() (preferred)
home_directory=$(pwd) file_count=$(ls -1 | wc -l)echo "Current user: $current_user"
echo "Current directory: $home_directory"
echo "File count: $file_count"
`
Special Variables
Bash provides several built-in variables:
`bash
#!/bin/bash
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: $?"
`
User Input and Interactive Scripts
Make your scripts interactive by accepting user input:
`bash
#!/bin/bash
echo "What's your name?"
read user_name
echo "Hello, $user_name!"
Reading with a prompt
read -p "Enter your age: " user_age echo "You are $user_age years old."Silent input (for passwords)
read -s -p "Enter password: " password echo echo "Password entered (hidden)"`Conditional Statements
Conditional statements allow your scripts to make decisions based on different conditions.
Basic If-Else Structure
`bash
#!/bin/bash
read -p "Enter a number: " number
if [ $number -gt 10 ]; then
echo "Number is greater than 10"
elif [ $number -eq 10 ]; then
echo "Number equals 10"
else
echo "Number is less than 10"
fi
`
Comparison Operators
#### Numeric Comparisons
`bash
#!/bin/bash
a=5
b=10
if [ $a -eq $b ]; then echo "Equal"; fi
if [ $a -ne $b ]; then echo "Not equal"; fi
if [ $a -lt $b ]; then echo "$a is less than $b"; fi
if [ $a -le $b ]; then echo "$a is less than or equal to $b"; fi
if [ $a -gt $b ]; then echo "$a is greater than $b"; fi
if [ $a -ge $b ]; then echo "$a is greater than or equal to $b"; fi
`
#### String Comparisons
`bash
#!/bin/bash
string1="hello"
string2="world"
if [ "$string1" = "$string2" ]; then echo "Strings are equal" else echo "Strings are different" fi
Check if string is empty
if [ -z "$string1" ]; then echo "String is empty" else echo "String is not empty" fiCheck if string is not empty
if [ -n "$string1" ]; then echo "String has content" fi`File and Directory Tests
`bash
#!/bin/bash
file_path="/etc/passwd"
if [ -f "$file_path" ]; then echo "$file_path is a regular file" fi
if [ -d "/home" ]; then echo "/home is a directory" fi
if [ -r "$file_path" ]; then echo "$file_path is readable" fi
if [ -w "$file_path" ]; then echo "$file_path is writable" fi
if [ -x "$file_path" ]; then
echo "$file_path is executable"
fi
`
Logical Operators
`bash
#!/bin/bash
age=25
country="USA"
AND operator
if [ $age -ge 18 ] && [ "$country" = "USA" ]; then echo "Eligible to vote in the USA" fiOR operator
if [ $age -lt 13 ] || [ $age -gt 65 ]; then echo "Special age category" fiNOT operator
if [ ! -f "nonexistent.txt" ]; then echo "File does not exist" fi`Loops: Automating Repetitive Tasks
Loops are essential for automating repetitive operations and processing multiple items.
For Loops
#### Basic For Loop
`bash
#!/bin/bash
Loop through a list of items
for fruit in apple banana cherry date; do echo "I like $fruit" doneLoop through numbers
for i in {1..5}; do echo "Number: $i" doneLoop with step
for i in {0..10..2}; do echo "Even number: $i" done`#### C-Style For Loop
`bash
#!/bin/bash
Traditional C-style loop
for ((i=1; i<=10; i++)); do echo "Iteration $i" doneCalculating factorial
read -p "Enter a number: " num factorial=1for ((i=1; i<=num; i++)); do factorial=$((factorial * i)) done
echo "Factorial of $num is $factorial"
`
#### Looping Through Files
`bash
#!/bin/bash
Process all .txt files in current directory
for file in *.txt; do if [ -f "$file" ]; then echo "Processing: $file" # Add your file processing commands here wc -l "$file" fi doneLoop through command line arguments
for arg in "$@"; do echo "Argument: $arg" done`While Loops
`bash
#!/bin/bash
Basic while loop
counter=1 while [ $counter -le 5 ]; do echo "Count: $counter" counter=$((counter + 1)) doneReading file line by line
while IFS= read -r line; do echo "Line: $line" done < "input.txt"Menu system using while loop
while true; do echo "1. List files" echo "2. Show date" echo "3. Exit" read -p "Choose option: " choice case $choice in 1) ls -la ;; 2) date ;; 3) echo "Goodbye!"; break ;; *) echo "Invalid option" ;; esac done`Until Loops
`bash
#!/bin/bash
Until loop (continues until condition becomes true)
counter=1 until [ $counter -gt 5 ]; do echo "Counter: $counter" counter=$((counter + 1)) doneWaiting for a file to appear
until [ -f "important_file.txt" ]; do echo "Waiting for file to appear..." sleep 2 done echo "File found!"`File Operations
File manipulation is a crucial aspect of shell scripting, enabling you to process data, manage configurations, and handle system files.
Reading Files
`bash
#!/bin/bash
Read entire file content
file_content=$(cat "sample.txt") echo "$file_content"Read file line by line with line numbers
line_number=1 while IFS= read -r line; do echo "$line_number: $line" line_number=$((line_number + 1)) done < "sample.txt"Process CSV file
while IFS=',' read -r name age city; do echo "Name: $name, Age: $age, City: $city" done < "data.csv"`Writing to Files
`bash
#!/bin/bash
Write to file (overwrites existing content)
echo "Hello World" > output.txtAppend to file
echo "Second line" >> output.txt echo "Third line" >> output.txtWrite multiple lines
cat << EOF > multi_line.txt This is line one This is line two This is line three EOFCreate a log entry
log_entry="$(date): Script executed successfully" echo "$log_entry" >> script.log`File Testing and Information
`bash
#!/bin/bash
filename="test.txt"
Check if file exists and get information
if [ -f "$filename" ]; then echo "File exists" echo "Size: $(stat -c%s "$filename") bytes" echo "Last modified: $(stat -c%y "$filename")" echo "Permissions: $(stat -c%A "$filename")" echo "Line count: $(wc -l < "$filename")" echo "Word count: $(wc -w < "$filename")" else echo "File does not exist" fi`Directory Operations
`bash
#!/bin/bash
Create directory structure
mkdir -p projects/web/css mkdir -p projects/web/js mkdir -p projects/web/imagesNavigate and list contents
cd projects echo "Contents of projects directory:" ls -laFind files with specific patterns
echo "Finding all .txt files:" find . -name "*.txt" -type fDirectory size
echo "Directory size:" du -sh .Copy files with backup
cp important.txt important.txt.backup`Functions: Organizing Your Code
Functions help organize code, reduce repetition, and make scripts more maintainable.
Basic Function Syntax
`bash
#!/bin/bash
Function definition
greet() { echo "Hello, $1!" echo "Welcome to shell scripting" }Function call
greet "Alice" greet "Bob"`Functions with Parameters and Return Values
`bash
#!/bin/bash
Function with multiple parameters
calculate_area() { local length=$1 local width=$2 local area=$((length * width)) echo $area }Function with return value
is_even() { local number=$1 if [ $((number % 2)) -eq 0 ]; then return 0 # true else return 1 # false fi }Using functions
read -p "Enter length: " len read -p "Enter width: " wid area=$(calculate_area $len $wid) echo "Area: $area"read -p "Enter a number: " num
if is_even $num; then
echo "$num is even"
else
echo "$num is odd"
fi
`
Advanced Function Features
`bash
#!/bin/bash
Function with local variables
process_data() { local input_file=$1 local output_file=$2 local temp_file="/tmp/processing_$" echo "Processing $input_file..." # Process data (example: convert to uppercase) tr '[:lower:]' '[:upper:]' < "$input_file" > "$temp_file" mv "$temp_file" "$output_file" echo "Processing complete. Output saved to $output_file" }Recursive function example
factorial() { local n=$1 if [ $n -le 1 ]; then echo 1 else local prev=$(factorial $((n - 1))) echo $((n * prev)) fi }Usage
process_data "input.txt" "output.txt" result=$(factorial 5) echo "Factorial of 5 is: $result"`Practical Automation Examples
Let's explore real-world automation scenarios that demonstrate the power of shell scripting.
System Monitoring Script
`bash
#!/bin/bash
System monitoring and reporting script
generate_report() { local report_file="system_report_$(date +%Y%m%d_%H%M%S).txt" { echo "=== SYSTEM REPORT ===" echo "Generated on: $(date)" echo "" echo "=== SYSTEM INFORMATION ===" echo "Hostname: $(hostname)" echo "Uptime: $(uptime)" echo "Kernel: $(uname -r)" echo "" echo "=== CPU INFORMATION ===" echo "CPU Usage:" top -bn1 | grep "Cpu(s)" | awk '{print $2 $3 $4 $5 $6 $7 $8}' echo "" echo "=== MEMORY USAGE ===" free -h echo "" echo "=== DISK USAGE ===" df -h echo "" echo "=== TOP PROCESSES ===" ps aux --sort=-%cpu | head -10 echo "" echo "=== NETWORK CONNECTIONS ===" netstat -tuln | head -10 } > "$report_file" echo "Report generated: $report_file" }
Check disk space and alert if low
check_disk_space() { local threshold=80 df -h | awk 'NR>1 {print $5 " " $1 " " $6}' | while read output; do usage=$(echo $output | awk '{print $1}' | sed 's/%//') partition=$(echo $output | awk '{print $2}') mount_point=$(echo $output | awk '{print $3}') if [ $usage -ge $threshold ]; then echo "WARNING: Disk usage on $partition ($mount_point) is ${usage}%" fi done }Main execution
echo "Starting system monitoring..." generate_report check_disk_space echo "Monitoring complete."`Backup Automation Script
`bash
#!/bin/bash
Automated backup script with rotation
Configuration
SOURCE_DIR="/home/user/documents" BACKUP_DIR="/backup" RETENTION_DAYS=7 LOG_FILE="/var/log/backup.log"Function to log messages
log_message() { echo "$(date '+%Y-%m-%d %H:%M:%S'): $1" | tee -a "$LOG_FILE" }Create backup
create_backup() { local timestamp=$(date +%Y%m%d_%H%M%S) local backup_name="backup_${timestamp}.tar.gz" local backup_path="${BACKUP_DIR}/${backup_name}" log_message "Starting backup of $SOURCE_DIR" # Create backup directory if it doesn't exist mkdir -p "$BACKUP_DIR" # Create compressed backup if tar -czf "$backup_path" -C "$(dirname "$SOURCE_DIR")" "$(basename "$SOURCE_DIR")"; then log_message "Backup created successfully: $backup_name" echo "$backup_path" else log_message "ERROR: Backup failed" return 1 fi }Remove old backups
cleanup_old_backups() { log_message "Cleaning up backups older than $RETENTION_DAYS days" find "$BACKUP_DIR" -name "backup_*.tar.gz" -type f -mtime +$RETENTION_DAYS -delete local remaining=$(find "$BACKUP_DIR" -name "backup_*.tar.gz" -type f | wc -l) log_message "Cleanup complete. $remaining backup files remaining" }Verify backup integrity
verify_backup() { local backup_file=$1 log_message "Verifying backup integrity: $(basename "$backup_file")" if tar -tzf "$backup_file" > /dev/null 2>&1; then log_message "Backup verification successful" return 0 else log_message "ERROR: Backup verification failed" return 1 fi }Main backup process
main() { log_message "=== BACKUP PROCESS STARTED ===" # Check if source directory exists if [ ! -d "$SOURCE_DIR" ]; then log_message "ERROR: Source directory $SOURCE_DIR does not exist" exit 1 fi # Create backup backup_file=$(create_backup) if [ $? -eq 0 ] && [ -n "$backup_file" ]; then # Verify backup if verify_backup "$backup_file"; then # Cleanup old backups cleanup_old_backups log_message "=== BACKUP PROCESS COMPLETED SUCCESSFULLY ===" else log_message "=== BACKUP PROCESS FAILED (VERIFICATION) ===" exit 1 fi else log_message "=== BACKUP PROCESS FAILED (CREATION) ===" exit 1 fi }Execute main function
main`Log Processing and Analysis Script
`bash
#!/bin/bash
Log analysis and processing script
LOG_FILE="/var/log/apache2/access.log" REPORT_DIR="/tmp/log_reports" DATE_FILTER=$(date +%d/%b/%Y)
Initialize report directory
mkdir -p "$REPORT_DIR"Function to analyze IP addresses
analyze_ips() { echo "=== TOP 10 IP ADDRESSES ===" awk '{print $1}' "$LOG_FILE" | sort | uniq -c | sort -nr | head -10 echo "" }Function to analyze requested pages
analyze_pages() { echo "=== TOP 10 REQUESTED PAGES ===" awk '{print $7}' "$LOG_FILE" | sort | uniq -c | sort -nr | head -10 echo "" }Function to analyze HTTP status codes
analyze_status_codes() { echo "=== HTTP STATUS CODE DISTRIBUTION ===" awk '{print $9}' "$LOG_FILE" | sort | uniq -c | sort -nr echo "" }Function to find error entries
find_errors() { echo "=== ERROR ENTRIES (4xx and 5xx) ===" awk '$9 ~ /^[45]/ {print $0}' "$LOG_FILE" | head -20 echo "" }Function to analyze bandwidth usage
analyze_bandwidth() { echo "=== BANDWIDTH USAGE BY IP ===" awk '{ ip = $1 bytes = $10 if (bytes ~ /^[0-9]+$/) { total[ip] += bytes } } END { for (ip in total) { printf "%s: %.2f MB\n", ip, total[ip]/1048576 } }' "$LOG_FILE" | sort -k2 -nr | head -10 echo "" }Generate comprehensive report
generate_report() { local report_file="${REPORT_DIR}/log_analysis_$(date +%Y%m%d_%H%M%S).txt" { echo "LOG ANALYSIS REPORT" echo "Generated on: $(date)" echo "Log file: $LOG_FILE" echo "Date filter: $DATE_FILTER" echo "==================================" echo "" analyze_ips analyze_pages analyze_status_codes find_errors analyze_bandwidth } > "$report_file" echo "Report generated: $report_file" }Check if log file exists
if [ ! -f "$LOG_FILE" ]; then echo "Error: Log file $LOG_FILE not found" exit 1 fiGenerate the report
generate_reportOptional: Send email notification (requires mail command)
mail -s "Log Analysis Report" admin@example.com < "$report_file"
`Best Practices and Tips
Error Handling and Debugging
`bash
#!/bin/bash
Enable strict error handling
set -euo pipefailFunction for error handling
handle_error() { echo "Error occurred in script at line $1" exit 1 }Set up error trap
trap 'handle_error $LINENO' ERRDebugging techniques
Uncomment the following line for debug output
set -x
Validate input parameters
validate_input() { if [ $# -ne 2 ]; then echo "Usage: $0Example usage
validate_input "$@" echo "Processing files: $1 -> $2"`Security Considerations
`bash
#!/bin/bash
Security best practices
Use full paths for commands
GREP_CMD="/bin/grep" AWK_CMD="/usr/bin/awk"Validate and sanitize input
sanitize_input() { local input=$1 # Remove potentially dangerous characters echo "$input" | sed 's/[;&|`$()]//g' }Use temporary files securely
create_temp_file() { local temp_file temp_file=$(mktemp) || { echo "Error: Cannot create temporary file" exit 1 } echo "$temp_file" }Set secure permissions
secure_file() { local file=$1 chmod 600 "$file" chown "$(whoami)" "$file" }`Performance Optimization
`bash
#!/bin/bash
Performance optimization techniques
Use built-in commands when possible
Instead of: cat file.txt | grep pattern
Use: grep pattern file.txt
Avoid unnecessary subprocess creation
Instead of: result=$(echo $var | cut -c1-5)
Use: result=${var:0:5}
Efficient file processing
process_large_file() { local file=$1 # Use while read for large files instead of for loops while IFS= read -r line; do # Process line echo "Processing: $line" done < "$file" }Use arrays for multiple values
declare -a servers=("web1" "web2" "db1") for server in "${servers[@]}"; do echo "Checking $server" # ping -c1 "$server" > /dev/null && echo "$server is up" done`Advanced Scripting Techniques
Working with Arrays
`bash
#!/bin/bash
Array operations
Declare arrays
fruits=("apple" "banana" "cherry") declare -a numbers=(1 2 3 4 5)Add elements
fruits+=("date") fruits[10]="elderberry"Access elements
echo "First fruit: ${fruits[0]}" echo "All fruits: ${fruits[@]}" echo "Array length: ${#fruits[@]}"Loop through array
for fruit in "${fruits[@]}"; do echo "Fruit: $fruit" doneAssociative arrays (bash 4+)
declare -A colors colors[red]="#FF0000" colors[green]="#00FF00" colors[blue]="#0000FF"for color in "${!colors[@]}"; do
echo "$color: ${colors[$color]}"
done
`
Signal Handling
`bash
#!/bin/bash
Signal handling example
Cleanup function
cleanup() { echo "Cleaning up..." # Remove temporary files rm -f /tmp/script_temp_* echo "Cleanup complete" exit 0 }Set signal traps
trap cleanup SIGINT SIGTERMLong-running process simulation
counter=0 while true; do echo "Working... $counter" sleep 2 counter=$((counter + 1)) # Create some temporary work echo "Temporary data" > "/tmp/script_temp_$counter" done`Conclusion
Shell scripting in Linux is a powerful skill that opens up countless possibilities for automation, system administration, and productivity enhancement. Throughout this comprehensive guide, we've covered the fundamental concepts and advanced techniques that form the foundation of effective bash scripting.
Key takeaways from this tutorial include:
1. Master the Basics: Understanding variables, user input, and script structure provides the foundation for all shell scripting endeavors.
2. Control Flow Mastery: Conditional statements and loops are essential for creating dynamic, responsive scripts that can handle various scenarios and process data efficiently.
3. File Operations: The ability to read, write, and manipulate files is crucial for most automation tasks and system administration scripts.
4. Function Organization: Well-structured functions make scripts more maintainable, reusable, and easier to debug.
5. Real-World Application: The practical examples demonstrate how these concepts combine to solve actual problems in system monitoring, backup automation, and log analysis.
6. Best Practices: Following security guidelines, implementing error handling, and optimizing performance ensures your scripts are robust and reliable.
As you continue your shell scripting journey, remember that practice is key to mastery. Start with simple scripts and gradually incorporate more complex features as you become comfortable with the basics. The Linux command line offers an incredibly rich environment for automation, and shell scripting is your gateway to harnessing that power effectively.
Whether you're automating daily tasks, managing servers, or processing data, the skills covered in this guide will serve as a solid foundation for your continued growth in Linux system administration and automation. Keep experimenting, learning, and building upon these concepts to become proficient in the art of shell scripting.