Introduction
This comprehensive guide contains 100+ Bash exercises ranging from beginner to advanced levels. Each exercise includes a problem statement, hints, and a complete solution. These exercises cover all major Bash concepts including variables, loops, conditionals, functions, file operations, text processing, and more.
Table of Contents
- Basic Exercises
- Variables and Data Types
- Conditional Statements
- Loop Exercises
- Function Exercises
- File Operations
- Text Processing
- Arrays and Data Structures
- Advanced Scripting
- System Administration
- Real-World Projects
1. Basic Exercises
Exercise 1.1: Hello World
Problem: Write a script that prints "Hello, World!" to the console.
Hint: Use the echo command.
Solution:
#!/bin/bash echo "Hello, World!"
Exercise 1.2: User Input
Problem: Write a script that asks the user for their name and greets them.
Hint: Use read to get user input.
Solution:
#!/bin/bash echo "What is your name?" read name echo "Hello, $name! Nice to meet you."
Exercise 1.3: Command Line Arguments
Problem: Write a script that accepts two command-line arguments and prints them.
Hint: Use $1 and $2 for the first two arguments.
Solution:
#!/bin/bash echo "First argument: $1" echo "Second argument: $2" echo "Total arguments: $#" echo "All arguments: $@"
Exercise 1.4: Simple Calculator
Problem: Write a script that takes two numbers as input and prints their sum.
Hint: Use arithmetic expansion $((...)).
Solution:
#!/bin/bash echo "Enter first number:" read num1 echo "Enter second number:" read num2 sum=$((num1 + num2)) echo "Sum: $sum"
Exercise 1.5: Current Date and Time
Problem: Write a script that displays the current date and time in a readable format.
Hint: Use the date command.
Solution:
#!/bin/bash echo "Current date and time:" date echo "Formatted: $(date '+%Y-%m-%d %H:%M:%S')"
2. Variables and Data Types
Exercise 2.1: String Concatenation
Problem: Write a script that concatenates two strings entered by the user.
Hint: Simply place variables next to each other.
Solution:
#!/bin/bash echo "Enter first string:" read str1 echo "Enter second string:" read str2 result="$str1$str2" echo "Concatenated: $result"
Exercise 2.2: String Length
Problem: Write a script that calculates and prints the length of a string entered by the user.
Hint: Use ${#variable} syntax.
Solution:
#!/bin/bash
echo "Enter a string:"
read str
length=${#str}
echo "The string '$str' has $length characters."
Exercise 2.3: Substring Extraction
Problem: Extract and print the first 5 characters of a string, then characters from position 3 to 7.
Hint: Use ${variable:position:length}.
Solution:
#!/bin/bash
echo "Enter a string (at least 10 characters):"
read str
first5="${str:0:5}"
mid="${str:2:5}"
echo "First 5 characters: $first5"
echo "Characters 3-7: $mid"
Exercise 2.4: Uppercase/Lowercase Conversion
Problem: Convert a string to uppercase and lowercase.
Hint: Use tr command or Bash 4+ ${var^^} and ${var,,}.
Solution:
#!/bin/bash
echo "Enter a string:"
read str
# Method 1: Using tr
echo "Uppercase (tr): $(echo "$str" | tr '[:lower:]' '[:upper:]')"
echo "Lowercase (tr): $(echo "$str" | tr '[:upper:]' '[:lower:]')"
# Method 2: Bash 4+ built-in
echo "Uppercase (bash): ${str^^}"
echo "Lowercase (bash): ${str,,}"
Exercise 2.5: Variable Default Values
Problem: Write a script that uses a variable with a default value if not set.
Hint: Use ${var:-default} syntax.
Solution:
#!/bin/bash
# Set a variable (comment out to test default)
# name="John"
echo "Hello, ${name:-World}!"
# Assign default if not set
message="${1:-Default message}"
echo "Message: $message"
Exercise 2.6: Arithmetic Operations
Problem: Create a script that performs addition, subtraction, multiplication, and division on two numbers.
Hint: Use $((...)) for arithmetic.
Solution:
#!/bin/bash echo "Enter first number:" read a echo "Enter second number:" read b echo "Addition: $((a + b))" echo "Subtraction: $((a - b))" echo "Multiplication: $((a * b))" echo "Division: $((a / b))" echo "Modulus: $((a % b))" echo "Power: $((a ** 2)), $((b ** 2))"
Exercise 2.7: Floating Point Arithmetic
Problem: Perform floating-point division with 2 decimal places.
Hint: Use bc or awk for floating-point math.
Solution:
#!/bin/bash
echo "Enter first number:"
read a
echo "Enter second number:"
read b
# Using bc
result=$(echo "scale=2; $a / $b" | bc)
echo "Division (bc): $result"
# Using awk
result=$(awk "BEGIN {printf \"%.2f\", $a/$b}")
echo "Division (awk): $result"
3. Conditional Statements
Exercise 3.1: Even or Odd
Problem: Write a script that checks if a number is even or odd.
Hint: Use modulo operator % with if statement.
Solution:
#!/bin/bash echo "Enter a number:" read num if [ $((num % 2)) -eq 0 ]; then echo "$num is even" else echo "$num is odd" fi
Exercise 3.2: Number Comparison
Problem: Compare two numbers and print the larger one.
Hint: Use -gt, -lt, or -eq operators.
Solution:
#!/bin/bash echo "Enter first number:" read a echo "Enter second number:" read b if [ $a -gt $b ]; then echo "$a is greater than $b" elif [ $a -lt $b ]; then echo "$a is less than $b" else echo "$a equals $b" fi
Exercise 3.3: File Type Check
Problem: Check if a given path is a file, directory, or doesn't exist.
Hint: Use -f, -d, and -e test operators.
Solution:
#!/bin/bash echo "Enter a path:" read path if [ -e "$path" ]; then if [ -f "$path" ]; then echo "$path is a regular file" elif [ -d "$path" ]; then echo "$path is a directory" else echo "$path is some other type of file" fi else echo "$path does not exist" fi
Exercise 3.4: Grade Calculator
Problem: Convert a numerical score (0-100) to a letter grade (A, B, C, D, F).
Hint: Use elif chain for multiple conditions.
Solution:
#!/bin/bash echo "Enter your score (0-100):" read score if [ $score -ge 90 ]; then grade="A" elif [ $score -ge 80 ]; then grade="B" elif [ $score -ge 70 ]; then grade="C" elif [ $score -ge 60 ]; then grade="D" else grade="F" fi echo "Your grade is: $grade"
Exercise 3.5: Leap Year Check
Problem: Determine if a given year is a leap year.
Hint: Leap year rules: divisible by 4, but not by 100 unless also by 400.
Solution:
#!/bin/bash echo "Enter a year:" read year if [ $((year % 400)) -eq 0 ]; then echo "$year is a leap year" elif [ $((year % 100)) -eq 0 ]; then echo "$year is not a leap year" elif [ $((year % 4)) -eq 0 ]; then echo "$year is a leap year" else echo "$year is not a leap year" fi
Exercise 3.6: Case Statement Menu
Problem: Create a simple menu using case statement.
Hint: Use case $variable in pattern) commands;; esac.
Solution:
#!/bin/bash echo "Select an option:" echo "1) Display date" echo "2) Display calendar" echo "3) List files" echo "4) Current directory" echo "5) Exit" read choice case $choice in 1) date ;; 2) cal ;; 3) ls -la ;; 4) pwd ;; 5) echo "Goodbye!" exit 0 ;; *) echo "Invalid option" ;; esac
Exercise 3.7: Password Strength Checker
Problem: Check if a password meets criteria: length >= 8, contains uppercase, lowercase, and digit.
Hint: Use pattern matching and conditional operators.
Solution:
#!/bin/bash
echo "Enter a password:"
read -s password
# Check length
if [ ${#password} -lt 8 ]; then
echo "Password too short (min 8 characters)"
exit 1
fi
# Check for uppercase
if ! [[ "$password" =~ [A-Z] ]]; then
echo "Password must contain at least one uppercase letter"
exit 1
fi
# Check for lowercase
if ! [[ "$password" =~ [a-z] ]]; then
echo "Password must contain at least one lowercase letter"
exit 1
fi
# Check for digit
if ! [[ "$password" =~ [0-9] ]]; then
echo "Password must contain at least one digit"
exit 1
fi
echo "Password is strong!"
4. Loop Exercises
Exercise 4.1: Print Numbers 1 to 10
Problem: Print numbers from 1 to 10 using a loop.
Hint: Use for loop with {1..10}.
Solution:
#!/bin/bash
echo "Using for loop with range:"
for i in {1..10}; do
echo -n "$i "
done
echo
echo "Using C-style for loop:"
for ((i=1; i<=10; i++)); do
echo -n "$i "
done
echo
Exercise 4.2: Sum of First N Numbers
Problem: Calculate the sum of first N natural numbers.
Hint: Use a loop to accumulate sum.
Solution:
#!/bin/bash echo "Enter a number:" read n sum=0 for ((i=1; i<=n; i++)); do sum=$((sum + i)) done echo "Sum of first $n numbers: $sum" # Formula method (more efficient) formula_sum=$((n * (n + 1) / 2)) echo "Formula verification: $formula_sum"
Exercise 4.3: Multiplication Table
Problem: Print the multiplication table for a given number.
Hint: Loop from 1 to 10 and multiply.
Solution:
#!/bin/bash
echo "Enter a number:"
read num
echo "Multiplication table for $num:"
for i in {1..10}; do
result=$((num * i))
echo "$num × $i = $result"
done
Exercise 4.4: Factorial Calculation
Problem: Calculate the factorial of a number.
Hint: Multiply numbers from 1 to n.
Solution:
#!/bin/bash echo "Enter a number:" read n factorial=1 for ((i=1; i<=n; i++)); do factorial=$((factorial * i)) done echo "$n! = $factorial"
Exercise 4.5: Fibonacci Sequence
Problem: Generate the first N numbers in the Fibonacci sequence.
Hint: Each number is the sum of the two preceding ones.
Solution:
#!/bin/bash echo "How many Fibonacci numbers to generate?" read n a=0 b=1 echo "Fibonacci sequence:" for ((i=0; i<n; i++)); do echo -n "$a " fn=$((a + b)) a=$b b=$fn done echo
Exercise 4.6: Prime Number Check
Problem: Check if a number is prime.
Hint: Check divisibility from 2 to sqrt(n).
Solution:
#!/bin/bash echo "Enter a number:" read num if [ $num -lt 2 ]; then echo "$num is not prime" exit 0 fi is_prime=1 for ((i=2; i*i<=num; i++)); do if [ $((num % i)) -eq 0 ]; then is_prime=0 break fi done if [ $is_prime -eq 1 ]; then echo "$num is prime" else echo "$num is not prime" fi
Exercise 4.7: Print Pattern - Triangle
Problem: Print a right-angled triangle of stars.
Hint: Use nested loops.
Solution:
#!/bin/bash echo "Enter number of rows:" read rows for ((i=1; i<=rows; i++)); do for ((j=1; j<=i; j++)); do echo -n "* " done echo done # Inverted triangle echo -e "\nInverted triangle:" for ((i=rows; i>=1; i--)); do for ((j=1; j<=i; j++)); do echo -n "* " done echo done
Exercise 4.8: Pyramid Pattern
Problem: Print a centered pyramid of stars.
Hint: Calculate spaces and stars for each row.
Solution:
#!/bin/bash echo "Enter number of rows:" read rows for ((i=1; i<=rows; i++)); do # Print spaces for ((j=i; j<rows; j++)); do echo -n " " done # Print stars for ((j=1; j<=i; j++)); do echo -n "* " done echo done
Exercise 4.9: Read File Line by Line
Problem: Read a file line by line and display line numbers.
Hint: Use while read loop.
Solution:
#!/bin/bash echo "Enter filename:" read filename if [ ! -f "$filename" ]; then echo "File not found!" exit 1 fi line_num=1 while IFS= read -r line; do echo "$line_num: $line" ((line_num++)) done < "$filename"
Exercise 4.10: Process All Files in Directory
Problem: List all files in a directory with their sizes.
Hint: Loop through files with for file in *.
Solution:
#!/bin/bash
echo "Files in current directory:"
echo "============================"
for file in *; do
if [ -f "$file" ]; then
size=$(stat -c%s "$file" 2>/dev/null || stat -f%z "$file" 2>/dev/null)
echo "📄 $file - ${size} bytes"
elif [ -d "$file" ]; then
echo "📁 $file - (directory)"
fi
done
Exercise 4.11: Guess the Number Game
Problem: Create a number guessing game where the computer picks a random number.
Hint: Use $RANDOM and loop until correct.
Solution:
#!/bin/bash secret=$((RANDOM % 100 + 1)) attempts=0 echo "I'm thinking of a number between 1 and 100." echo "Can you guess it?" while true; do echo -n "Your guess: " read guess ((attempts++)) if [ $guess -lt $secret ]; then echo "Too low!" elif [ $guess -gt $secret ]; then echo "Too high!" else echo "Correct! You guessed it in $attempts attempts." break fi done
Exercise 4.12: Countdown Timer
Problem: Create a countdown timer from a given number.
Hint: Use while loop with sleep.
Solution:
#!/bin/bash echo "Enter countdown seconds:" read seconds while [ $seconds -gt 0 ]; do echo -ne "Time remaining: $seconds seconds\033[0K\r" sleep 1 ((seconds--)) done echo -e "\nTime's up!"
Exercise 4.13: Menu with Select Loop
Problem: Create an interactive menu using the select loop.
Hint: Use select for menu generation.
Solution:
#!/bin/bash
PS3="Choose an option: "
options=("Show Date" "Show Calendar" "List Files" "Show Disk Usage" "Exit")
select opt in "${options[@]}"; do
case $opt in
"Show Date")
date
;;
"Show Calendar")
cal
;;
"List Files")
ls -la
;;
"Show Disk Usage")
df -h
;;
"Exit")
echo "Goodbye!"
break
;;
*)
echo "Invalid option $REPLY"
;;
esac
echo -e "\nPress Enter to continue..."
read
done
Exercise 4.14: Infinite Loop with Break Condition
Problem: Create an infinite loop that breaks when the user types "quit".
Hint: Use while true and break.
Solution:
#!/bin/bash while true; do echo -n "Enter command (quit to exit): " read cmd if [ "$cmd" = "quit" ]; then echo "Exiting..." break fi echo "You entered: $cmd" eval "$cmd" done
5. Function Exercises
Exercise 5.1: Simple Function
Problem: Create a function that prints "Hello, World!"
Hint: Define function with function_name() { ... }.
Solution:
#!/bin/bash
greet() {
echo "Hello, World!"
}
# Call the function
greet
Exercise 5.2: Function with Parameters
Problem: Create a function that greets a person by name.
Hint: Access parameters with $1, $2, etc.
Solution:
#!/bin/bash
greet_person() {
echo "Hello, $1! How are you today?"
}
echo "Enter your name:"
read name
greet_person "$name"
Exercise 5.3: Function with Return Value
Problem: Create a function that adds two numbers and returns the result.
Hint: Use echo to return value, capture with command substitution.
Solution:
#!/bin/bash
add() {
local sum=$(( $1 + $2 ))
echo "$sum"
}
result=$(add 5 3)
echo "Sum: $result"
# Using return (for exit status only)
is_even() {
if [ $(( $1 % 2 )) -eq 0 ]; then
return 0 # true (success)
else
return 1 # false (failure)
fi
}
if is_even 4; then
echo "4 is even"
fi
Exercise 5.4: Factorial Function
Problem: Write a recursive function to calculate factorial.
Hint: Call the function from itself.
Solution:
#!/bin/bash
factorial() {
if [ $1 -le 1 ]; then
echo 1
else
local prev=$(factorial $(($1 - 1)))
echo $(($1 * prev))
fi
}
echo "Enter a number:"
read n
result=$(factorial $n)
echo "$n! = $result"
Exercise 5.5: Local vs Global Variables
Problem: Demonstrate local and global variable scope.
Hint: Use local keyword inside functions.
Solution:
#!/bin/bash
global_var="I'm global"
test_scope() {
local local_var="I'm local"
global_var="Modified global"
echo "Inside function:"
echo " global_var = $global_var"
echo " local_var = $local_var"
}
test_scope
echo "Outside function:"
echo " global_var = $global_var"
echo " local_var = $local_var" # Empty
Exercise 5.6: Calculator Using Functions
Problem: Create a calculator with separate functions for each operation.
Hint: Define functions for add, subtract, multiply, divide.
Solution:
#!/bin/bash
add() {
echo $(($1 + $2))
}
subtract() {
echo $(($1 - $2))
}
multiply() {
echo $(($1 * $2))
}
divide() {
if [ $2 -eq 0 ]; then
echo "Error: Division by zero"
return 1
fi
echo "scale=2; $1 / $2" | bc
}
# Main menu
while true; do
echo "1) Add"
echo "2) Subtract"
echo "3) Multiply"
echo "4) Divide"
echo "5) Exit"
read -p "Choose: " choice
if [ $choice -eq 5 ]; then
break
fi
read -p "Enter first number: " a
read -p "Enter second number: " b
case $choice in
1) result=$(add $a $b) ;;
2) result=$(subtract $a $b) ;;
3) result=$(multiply $a $b) ;;
4) result=$(divide $a $b) ;;
*) echo "Invalid choice"; continue ;;
esac
echo "Result: $result"
echo
done
Exercise 5.7: Validation Function
Problem: Create a function that validates if a string is a valid email.
Hint: Use regex pattern matching.
Solution:
#!/bin/bash
validate_email() {
local email="$1"
# Basic email regex
if [[ "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
return 0 # Valid
else
return 1 # Invalid
fi
}
echo "Enter email address:"
read email
if validate_email "$email"; then
echo "✓ Valid email address"
else
echo "✗ Invalid email address"
fi
Exercise 5.8: Array Processing Function
Problem: Create a function that finds the maximum value in an array.
Hint: Pass array as argument, use loop to find max.
Solution:
#!/bin/bash
find_max() {
local max=$1
shift
for num in "$@"; do
if [ $num -gt $max ]; then
max=$num
fi
done
echo $max
}
# Test array
numbers=(23 45 12 67 34 89 5)
echo "Array: ${numbers[@]}"
max=$(find_max "${numbers[@]}")
echo "Maximum value: $max"
Exercise 5.9: Function Library
Problem: Create a library of math functions in a separate file and source it.
Hint: Create a file with functions and use source or . to include.
Solution:
# mathlib.sh - Library file
#!/bin/bash
square() {
echo $(($1 * $1))
}
cube() {
echo $(($1 * $1 * $1))
}
is_prime() {
if [ $1 -lt 2 ]; then
return 1
fi
for ((i=2; i*i<=$1; i++)); do
if [ $(($1 % i)) -eq 0 ]; then
return 1
fi
done
return 0
}
# Main script
#!/bin/bash
source ./mathlib.sh # or . ./mathlib.sh
echo "Enter a number:"
read num
echo "Square: $(square $num)"
echo "Cube: $(cube $num)"
if is_prime $num; then
echo "$num is prime"
else
echo "$num is not prime"
fi
6. File Operations
Exercise 6.1: File Existence Check
Problem: Check if a file exists and is readable.
Hint: Use -e and -r test operators.
Solution:
#!/bin/bash echo "Enter filename:" read filename if [ -e "$filename" ]; then echo "✓ File exists" if [ -r "$filename" ]; then echo "✓ File is readable" else echo "✗ File is not readable" fi if [ -w "$filename" ]; then echo "✓ File is writable" else echo "✗ File is not writable" fi if [ -x "$filename" ]; then echo "✓ File is executable" else echo "✗ File is not executable" fi else echo "✗ File does not exist" fi
Exercise 6.2: File Backup
Problem: Create a backup of a file with timestamp.
Hint: Use cp and date command for timestamp.
Solution:
#!/bin/bash
echo "Enter filename to backup:"
read filename
if [ ! -f "$filename" ]; then
echo "File not found!"
exit 1
fi
timestamp=$(date +%Y%m%d_%H%M%S)
backup="${filename}_${timestamp}.bak"
cp "$filename" "$backup"
echo "Backup created: $backup"
# List backups
echo -e "\nExisting backups:"
ls -l "${filename}_"*.bak 2>/dev/null || echo "No backups found"
Exercise 6.3: File Information
Problem: Display detailed information about a file.
Hint: Use stat command or parse ls -l.
Solution:
#!/bin/bash echo "Enter filename:" read filename if [ ! -e "$filename" ]; then echo "File not found!" exit 1 fi echo "=== File Information ===" echo "Filename: $filename" echo "Full path: $(realpath "$filename")" echo "Size: $(stat -c%s "$filename" 2>/dev/null || stat -f%z "$filename") bytes" echo "Permissions: $(stat -c%A "$filename" 2>/dev/null || stat -f%Sp "$filename")" echo "Owner: $(stat -c%U "$filename" 2>/dev/null || stat -f%Su "$filename")" echo "Group: $(stat -c%G "$filename" 2>/dev/null || stat -f%Sg "$filename")" echo "Last modified: $(stat -c%y "$filename" 2>/dev/null || stat -f%Sm "$filename")"
Exercise 6.4: Line Count
Problem: Count the number of lines, words, and characters in a file.
Hint: Use wc command.
Solution:
#!/bin/bash echo "Enter filename:" read filename if [ ! -f "$filename" ]; then echo "File not found!" exit 1 fi lines=$(wc -l < "$filename") words=$(wc -w < "$filename") chars=$(wc -m < "$filename") echo "=== File Statistics ===" echo "Lines: $lines" echo "Words: $words" echo "Characters: $chars" # Display file preview echo -e "\n=== File Preview (first 5 lines) ===" head -5 "$filename"
Exercise 6.5: Search in Files
Problem: Search for a pattern in all .txt files in current directory.
Hint: Use grep with loop.
Solution:
#!/bin/bash echo "Enter search pattern:" read pattern found=0 for file in *.txt; do if [ -f "$file" ]; then if grep -q "$pattern" "$file"; then echo "✓ Pattern found in: $file" grep --color=auto -n "$pattern" "$file" | head -3 echo "---" ((found++)) fi fi done if [ $found -eq 0 ]; then echo "Pattern not found in any .txt files" else echo "Found in $found file(s)" fi
Exercise 6.6: File Organizer
Problem: Organize files by extension into subdirectories.
Hint: Create directories based on file extensions and move files.
Solution:
#!/bin/bash
echo "Organizing files by extension..."
for file in *; do
if [ -f "$file" ]; then
# Get extension
ext="${file##*.}"
# Skip if no extension
if [ "$ext" = "$file" ]; then
ext="no_extension"
fi
# Create directory if it doesn't exist
mkdir -p "$ext"
# Move file
mv -v "$file" "$ext/"
fi
done
echo "Organization complete!"
echo "Directories created:"
ls -ld */ 2>/dev/null
Exercise 6.7: Batch Rename Files
Problem: Rename all .txt files to add a prefix "backup_".
Hint: Loop through files and use mv.
Solution:
#!/bin/bash
prefix="backup_"
echo "Renaming .txt files with prefix '$prefix'..."
count=0
for file in *.txt; do
if [ -f "$file" ]; then
newname="${prefix}${file}"
mv -v "$file" "$newname"
((count++))
fi
done
echo "Renamed $count file(s)"
# Undo function
undo_rename() {
for file in ${prefix}*.txt; do
if [ -f "$file" ]; then
original="${file#$prefix}"
mv -v "$file" "$original"
fi
done
}
Exercise 6.8: Directory Tree
Problem: Create a directory tree structure.
Hint: Use mkdir -p for nested directories.
Solution:
#!/bin/bash
echo "Creating project structure..."
mkdir -p project/{src,bin,docs,tests,config}
mkdir -p project/src/{main,utils,models}
mkdir -p project/tests/{unit,integration}
# Create some placeholder files
touch project/src/main/app.py
touch project/src/utils/helpers.py
touch project/README.md
touch project/.gitignore
touch project/config/{dev,prod}.conf
echo "Project structure created:"
tree project 2>/dev/null || find project -type d | sort
Exercise 6.9: File Monitor
Problem: Monitor a file for changes and display new lines.
Hint: Use tail -f or loop with diff.
Solution:
#!/bin/bash echo "Enter filename to monitor:" read filename if [ ! -f "$filename" ]; then echo "File not found!" exit 1 fi echo "Monitoring $filename for changes (Ctrl+C to stop)" echo "==================================================" # Method 1: Using tail -f tail -f "$filename" # Method 2: Manual monitoring with diff # last_size=$(stat -c%s "$filename") # while true; do # current_size=$(stat -c%s "$filename") # if [ $current_size -gt $last_size ]; then # echo "File changed!" # tail -c +$((last_size + 1)) "$filename" # last_size=$current_size # fi # sleep 1 # done
Exercise 6.10: CSV File Parser
Problem: Parse a CSV file and display specific columns.
Hint: Use IFS=',' read for parsing.
Solution:
#!/bin/bash
echo "Enter CSV filename:"
read filename
if [ ! -f "$filename" ]; then
echo "File not found!"
exit 1
fi
# Read and display header
read header < "$filename"
echo "Columns:"
IFS=',' read -ra columns <<< "$header"
for i in "${!columns[@]}"; do
echo "$i: ${columns[$i]}"
done
echo -e "\nEnter column numbers to display (space-separated):"
read -a cols_to_show
echo -e "\nSelected data:"
line_num=1
while IFS=',' read -ra values; do
echo -n "Record $line_num: "
for col in "${cols_to_show[@]}"; do
echo -n "${values[$col]} "
done
echo
((line_num++))
done < "$filename"
7. Text Processing
Exercise 7.1: Word Count
Problem: Count occurrences of each word in a file.
Hint: Use tr to split words, then sort and uniq -c.
Solution:
#!/bin/bash echo "Enter filename:" read filename if [ ! -f "$filename" ]; then echo "File not found!" exit 1 fi echo "Word frequency:" echo "================" tr '[:space:]' '\n' < "$filename" | \ tr -d '[:punct:]' | \ tr '[:upper:]' '[:lower:]' | \ grep -v '^$' | \ sort | \ uniq -c | \ sort -rn | \ head -20
Exercise 7.2: Extract Email Addresses
Problem: Extract all email addresses from a file.
Hint: Use grep -o with email pattern.
Solution:
#!/bin/bash
echo "Enter filename:"
read filename
if [ ! -f "$filename" ]; then
echo "File not found!"
exit 1
fi
echo "Email addresses found:"
echo "======================="
grep -oE '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}' "$filename" | sort -u
count=$(grep -cE '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}' "$filename")
echo -e "\nTotal unique emails: $count"
Exercise 7.3: Log File Analyzer
Problem: Analyze a log file for error patterns.
Hint: Use grep to find errors and count occurrences.
Solution:
#!/bin/bash echo "Enter log filename:" read filename if [ ! -f "$filename" ]; then echo "File not found!" exit 1 fi echo "=== Log Analysis ===" echo "Total lines: $(wc -l < "$filename")" echo # Error levels echo "Error levels:" for level in ERROR WARN INFO DEBUG; do count=$(grep -c "$level" "$filename") echo " $level: $count" done echo # Top error messages echo "Top error messages:" grep -i error "$filename" | \ sed 's/.*ERROR: //' | \ cut -d' ' -f1-5 | \ sort | \ uniq -c | \ sort -rn | \ head -10 # Errors by hour echo -e "\nErrors by hour:" grep -i error "$filename" | \ grep -o ' [0-2][0-9]:[0-5][0-9]:[0-5][0-9]' | \ cut -d: -f1 | \ sort | \ uniq -c
Exercise 7.4: Find and Replace
Problem: Replace all occurrences of a string in a file.
Hint: Use sed for substitution.
Solution:
#!/bin/bash echo "Enter filename:" read filename echo "Enter search string:" read search echo "Enter replacement string:" read replace if [ ! -f "$filename" ]; then echo "File not found!" exit 1 fi echo "Preview of changes (first 5 lines):" sed "s/$search/$replace/g" "$filename" | head -5 echo -e "\nMake changes? (y/n)" read answer if [ "$answer" = "y" ]; then # Create backup cp "$filename" "$filename.bak" echo "Backup created: $filename.bak" # Perform replacement sed -i "s/$search/$replace/g" "$filename" echo "Replacement complete!" # Show changes echo -e "\nChanged lines:" diff "$filename.bak" "$filename" | grep '^>' else echo "Operation cancelled" fi
Exercise 7.5: CSV to JSON Converter
Problem: Convert a CSV file to JSON format.
Hint: Parse CSV and build JSON structure.
Solution:
#!/bin/bash
echo "Enter CSV filename:"
read filename
if [ ! -f "$filename" ]; then
echo "File not found!"
exit 1
fi
output="${filename%.csv}.json"
# Read header
read header < "$filename"
IFS=',' read -ra columns <<< "$header"
echo "Converting $filename to JSON..."
# Start JSON array
echo "[" > "$output"
line_num=1
while IFS=',' read -ra values; do
# Skip header
if [ $line_num -eq 1 ]; then
((line_num++))
continue
fi
# Start object
echo " {" >> "$output"
# Add fields
for i in "${!columns[@]}"; do
comma=","
if [ $i -eq $((${#columns[@]}-1)) ]; then
comma=""
fi
echo " \"${columns[$i]}\": \"${values[$i]}\"$comma" >> "$output"
done
# End object
comma=","
if [ $line_num -eq $(wc -l < "$filename") ]; then
comma=""
fi
echo " }$comma" >> "$output"
((line_num++))
done < "$filename"
# End JSON array
echo "]" >> "$output"
echo "Conversion complete! Output saved to $output"
Exercise 7.6: Text Wrapper
Problem: Wrap text at a specified column width.
Hint: Use fold command or implement custom wrapping.
Solution:
#!/bin/bash
echo "Enter filename:"
read filename
echo "Enter maximum line width (default 80):"
read width
width=${width:-80}
if [ ! -f "$filename" ]; then
echo "File not found!"
exit 1
fi
echo "Wrapped text (width $width):"
echo "=============================="
while IFS= read -r line; do
while [ ${#line} -gt $width ]; do
# Find last space before width
pos=$width
while [ $pos -gt 0 ] && [ "${line:$pos:1}" != " " ]; do
((pos--))
done
if [ $pos -eq 0 ]; then
# No space found, force break at width
pos=$width
fi
echo "${line:0:$pos}"
line="${line:$pos}"
# Remove leading space
line="${line# }"
done
echo "$line"
done < "$filename"
Exercise 7.7: Remove Duplicate Lines
Problem: Remove duplicate lines from a file while preserving order.
Hint: Use awk or associative array.
Solution:
#!/bin/bash
echo "Enter filename:"
read filename
if [ ! -f "$filename" ]; then
echo "File not found!"
exit 1
fi
output="${filename%.txt}_unique.txt"
echo "Removing duplicates (first occurrence preserved)..."
# Using awk
awk '!seen[$0]++' "$filename" > "$output"
original=$(wc -l < "$filename")
unique=$(wc -l < "$output")
removed=$((original - unique))
echo "Original lines: $original"
echo "Unique lines: $unique"
echo "Duplicates removed: $removed"
echo "Output saved to $output"
# Show preview
echo -e "\nPreview of cleaned file (first 10 lines):"
head -10 "$output"
8. Arrays and Data Structures
Exercise 8.1: Array Basics
Problem: Create and manipulate a basic indexed array.
Hint: Declare array with () and access with ${array[index]}.
Solution:
#!/bin/bash
# Create array
fruits=("apple" "banana" "orange" "grape" "mango")
# Display all elements
echo "All fruits: ${fruits[@]}"
# Display individual elements
echo "First fruit: ${fruits[0]}"
echo "Third fruit: ${fruits[2]}"
# Array length
echo "Number of fruits: ${#fruits[@]}"
# Array indices
echo "Indices: ${!fruits[@]}"
# Add element
fruits+=("pineapple")
echo "After adding: ${fruits[@]}"
# Remove element
unset fruits[1]
echo "After removing index 1: ${fruits[@]}"
Exercise 8.2: Array Operations
Problem: Perform common array operations: sum, average, min, max.
Hint: Loop through array elements.
Solution:
#!/bin/bash
numbers=(23 45 12 67 34 89 5 18 42)
echo "Array: ${numbers[@]}"
# Sum
sum=0
for num in "${numbers[@]}"; do
sum=$((sum + num))
done
echo "Sum: $sum"
# Average
avg=$((sum / ${#numbers[@]}))
echo "Average: $avg"
# Min/Max
min=${numbers[0]}
max=${numbers[0]}
for num in "${numbers[@]}"; do
if [ $num -lt $min ]; then
min=$num
fi
if [ $num -gt $max ]; then
max=$num
fi
done
echo "Minimum: $min"
echo "Maximum: $max"
Exercise 8.3: Array Sorting
Problem: Sort an array of numbers.
Hint: Use bubble sort or pipe to sort.
Solution:
#!/bin/bash
numbers=(23 45 12 67 34 89 5 18 42)
echo "Original array: ${numbers[@]}"
# Method 1: Using sort command (works for simple cases)
sorted=($(printf '%s\n' "${numbers[@]}" | sort -n))
echo "Sorted (sort command): ${sorted[@]}"
# Method 2: Bubble sort
bubble_sort() {
local arr=("$@")
local n=${#arr[@]}
for ((i=0; i<n-1; i++)); do
for ((j=0; j<n-i-1; j++)); do
if [ ${arr[$j]} -gt ${arr[$((j+1))]} ]; then
# Swap
temp=${arr[$j]}
arr[$j]=${arr[$((j+1))]}
arr[$((j+1))]=$temp
fi
done
done
echo "${arr[@]}"
}
echo "Sorted (bubble sort): $(bubble_sort "${numbers[@]}")"
Exercise 8.4: Associative Arrays
Problem: Use associative arrays to store key-value pairs.
Hint: Declare with declare -A (Bash 4+).
Solution:
#!/bin/bash
# Requires Bash 4+
declare -A capitals
capitals["USA"]="Washington"
capitals["France"]="Paris"
capitals["Japan"]="Tokyo"
capitals["UK"]="London"
capitals["Germany"]="Berlin"
# List all keys
echo "Countries: ${!capitals[@]}"
# List all values
echo "Capitals: ${capitals[@]}"
# Iterate over key-value pairs
for country in "${!capitals[@]}"; do
echo "$country: ${capitals[$country]}"
done
# Get specific value
echo "Capital of France: ${capitals[France]}"
# Check if key exists
if [ -n "${capitals[Italy]+_}" ]; then
echo "Italy is in the list"
else
echo "Italy is not in the list"
fi
Exercise 8.5: Stack Implementation
Problem: Implement a stack data structure using arrays.
Hint: Use array with push/pop operations.
Solution:
#!/bin/bash
declare -a stack
push() {
stack+=("$1")
echo "Pushed: $1"
}
pop() {
if [ ${#stack[@]} -eq 0 ]; then
echo "Stack is empty"
return 1
fi
top_index=$((${#stack[@]} - 1))
popped="${stack[$top_index]}"
unset stack[$top_index]
# Reindex array
stack=("${stack[@]}")
echo "Popped: $popped"
}
peek() {
if [ ${#stack[@]} -eq 0 ]; then
echo "Stack is empty"
else
echo "Top element: ${stack[-1]}"
fi
}
# Test the stack
push 10
push 20
push 30
peek
echo "Stack size: ${#stack[@]}"
pop
peek
echo "Stack: ${stack[@]}"
Exercise 8.6: Queue Implementation
Problem: Implement a queue data structure.
Hint: Use array with enqueue/dequeue operations.
Solution:
#!/bin/bash
declare -a queue
enqueue() {
queue+=("$1")
echo "Enqueued: $1"
}
dequeue() {
if [ ${#queue[@]} -eq 0 ]; then
echo "Queue is empty"
return 1
fi
dequeued="${queue[0]}"
# Remove first element
queue=("${queue[@]:1}")
echo "Dequeued: $dequeued"
}
front() {
if [ ${#queue[@]} -eq 0 ]; then
echo "Queue is empty"
else
echo "Front element: ${queue[0]}"
fi
}
# Test the queue
enqueue "first"
enqueue "second"
enqueue "third"
front
echo "Queue size: ${#queue[@]}"
dequeue
front
echo "Queue: ${queue[@]}"
Exercise 8.7: Matrix Operations
Problem: Create and manipulate a 2D matrix.
Hint: Use arrays of arrays or indexed arithmetic.
Solution:
#!/bin/bash
# Create a 3x3 matrix
declare -a matrix
fill_matrix() {
local val=1
for ((i=0; i<3; i++)); do
for ((j=0; j<3; j++)); do
matrix[$((i*3 + j))]=$val
((val++))
done
done
}
print_matrix() {
for ((i=0; i<3; i++)); do
for ((j=0; j<3; j++)); do
echo -n "${matrix[$((i*3 + j))]} "
done
echo
done
}
get_element() {
local row=$1
local col=$2
echo "${matrix[$((row*3 + col))]}"
}
set_element() {
local row=$1
local col=$2
local val=$3
matrix[$((row*3 + col))]=$val
}
fill_matrix
echo "Original matrix:"
print_matrix
set_element 1 1 99
echo -e "\nAfter setting element [1,1] to 99:"
print_matrix
echo -e "\nElement at [0,2]: $(get_element 0 2)"
9. Advanced Scripting
Exercise 9.1: Command Line Options Parser
Problem: Parse command line options with getopts.
Hint: Use getopts in a while loop.
Solution:
#!/bin/bash
usage() {
echo "Usage: $0 [-h] [-v] [-f filename] [-n count]"
echo " -h Show help"
echo " -v Verbose mode"
echo " -f Specify filename"
echo " -n Number of iterations"
}
verbose=0
filename=""
count=1
while getopts "hvf:n:" opt; do
case $opt in
h)
usage
exit 0
;;
v)
verbose=1
;;
f)
filename="$OPTARG"
;;
n)
count="$OPTARG"
;;
\?)
echo "Invalid option: -$OPTARG" >&2
usage
exit 1
;;
:)
echo "Option -$OPTARG requires an argument" >&2
exit 1
;;
esac
done
echo "Verbose mode: $verbose"
echo "Filename: $filename"
echo "Count: $count"
Exercise 9.2: Signal Handling
Problem: Handle signals like SIGINT (Ctrl+C) gracefully.
Hint: Use trap to catch signals.
Solution:
#!/bin/bash
cleanup() {
echo -e "\nCleaning up temporary files..."
rm -f /tmp/temp_$$.*
echo "Done. Exiting."
exit 0
}
# Set trap for multiple signals
trap cleanup SIGINT SIGTERM EXIT
# Create temporary files
echo "Creating temporary files..."
touch /tmp/temp_$$.log
touch /tmp/temp_$$.tmp
echo "Files created. Press Ctrl+C to interrupt."
# Simulate work
count=0
while true; do
echo "Working... ($count)"
sleep 1
((count++))
done
Exercise 9.3: Multi-threading with Background Jobs
Problem: Run multiple tasks in parallel using background jobs.
Hint: Use & to run jobs in background and wait to synchronize.
Solution:
#!/bin/bash
task() {
local id=$1
local duration=$((RANDOM % 5 + 1))
echo "Task $id starting (will run for $duration seconds)"
sleep $duration
echo "Task $id completed"
return $((id * 10))
}
echo "Starting parallel tasks..."
# Start tasks in background
task 1 &
pid1=$!
task 2 &
pid2=$!
task 3 &
pid3=$!
echo "Waiting for tasks to complete..."
# Wait for specific task
wait $pid1
echo "Task 1 finished with status $?"
# Wait for all remaining tasks
wait
echo "All tasks completed!"
# Job control
jobs
Exercise 9.4: Progress Bar
Problem: Create a progress bar for long-running operations.
Hint: Use printf with carriage return \r.
Solution:
#!/bin/bash
progress_bar() {
local current=$1
local total=$2
local width=50
percent=$((current * 100 / total))
completed=$((current * width / total))
remaining=$((width - completed))
printf "\rProgress: ["
printf "%${completed}s" | tr ' ' '#'
printf "%${remaining}s" | tr ' ' '-'
printf "] %d%%" $percent
}
# Simulate work
total=100
for ((i=1; i<=total; i++)); do
# Some work here
sleep 0.1
progress_bar $i $total
done
echo -e "\nDone!"
Exercise 9.5: Config File Parser
Problem: Parse a configuration file with key=value pairs.
Hint: Read file line by line and parse with IFS.
Solution:
#!/bin/bash
CONFIG_FILE="app.conf"
# Create sample config if not exists
if [ ! -f "$CONFIG_FILE" ]; then
cat > "$CONFIG_FILE" << 'EOF'
# Application configuration
DB_HOST=localhost
DB_PORT=5432
DB_NAME=myapp
MAX_CONNECTIONS=100
DEBUG=true
LOG_FILE=/var/log/app.log
EOF
echo "Created sample config file: $CONFIG_FILE"
fi
# Parse config file
declare -A config
while IFS='=' read -r key value; do
# Skip comments and empty lines
[[ "$key" =~ ^#.*$ || -z "$key" ]] && continue
# Trim whitespace
key=$(echo "$key" | xargs)
value=$(echo "$value" | xargs)
config["$key"]="$value"
done < "$CONFIG_FILE"
# Display configuration
echo "=== Configuration ==="
for key in "${!config[@]}"; do
echo "$key = ${config[$key]}"
done
# Use configuration
echo -e "\n=== Using Configuration ==="
echo "Connecting to ${config[DB_HOST]}:${config[DB_PORT]}"
echo "Database: ${config[DB_NAME]}"
echo "Debug mode: ${config[DEBUG]}"
Exercise 9.6: Interactive Menu System
Problem: Create a multi-level interactive menu system.
Hint: Use functions for each menu level.
Solution:
#!/bin/bash
main_menu() {
while true; do
clear
echo "=== Main Menu ==="
echo "1) System Information"
echo "2) File Operations"
echo "3) Network Tools"
echo "4) Exit"
read -p "Choice: " choice
case $choice in
1) system_menu ;;
2) file_menu ;;
3) network_menu ;;
4) echo "Goodbye!"; exit 0 ;;
*) echo "Invalid choice"; sleep 1 ;;
esac
done
}
system_menu() {
while true; do
clear
echo "=== System Information ==="
echo "1) Disk Usage"
echo "2) Memory Usage"
echo "3) CPU Info"
echo "4) Back to Main"
read -p "Choice: " choice
case $choice in
1) df -h; read -p "Press Enter to continue..." ;;
2) free -h; read -p "Press Enter to continue..." ;;
3) lscpu | head -10; read -p "Press Enter to continue..." ;;
4) break ;;
*) echo "Invalid choice"; sleep 1 ;;
esac
done
}
file_menu() {
while true; do
clear
echo "=== File Operations ==="
echo "1) List Files"
echo "2) Show Current Directory"
echo "3) Create Directory"
echo "4) Back to Main"
read -p "Choice: " choice
case $choice in
1) ls -la; read -p "Press Enter to continue..." ;;
2) pwd; read -p "Press Enter to continue..." ;;
3)
read -p "Enter directory name: " dirname
mkdir -p "$dirname"
echo "Directory created"
sleep 1
;;
4) break ;;
*) echo "Invalid choice"; sleep 1 ;;
esac
done
}
network_menu() {
while true; do
clear
echo "=== Network Tools ==="
echo "1) Ping Host"
echo "2) Show Network Interfaces"
echo "3) DNS Lookup"
echo "4) Back to Main"
read -p "Choice: " choice
case $choice in
1)
read -p "Enter host: " host
ping -c 4 "$host"
read -p "Press Enter to continue..."
;;
2) ip addr show; read -p "Press Enter to continue..." ;;
3)
read -p "Enter domain: " domain
nslookup "$domain"
read -p "Press Enter to continue..."
;;
4) break ;;
*) echo "Invalid choice"; sleep 1 ;;
esac
done
}
# Start the application
main_menu
10. System Administration
Exercise 10.1: Disk Usage Monitor
Problem: Monitor disk usage and alert if threshold exceeded.
Hint: Use df and parse percentage.
Solution:
#!/bin/bash
THRESHOLD=80
EMAIL="admin@localhost"
check_disk_usage() {
df -h | grep -vE '^Filesystem|tmpfs|cdrom' | while read line; do
usage=$(echo "$line" | awk '{print $5}' | sed 's/%//')
mount=$(echo "$line" | awk '{print $6}')
fs=$(echo "$line" | awk '{print $1}')
if [ $usage -ge $THRESHOLD ]; then
echo "WARNING: Filesystem $fs mounted on $mount is at ${usage}% capacity"
# Send alert (requires mail setup)
# echo "Filesystem $fs on $mount is at ${usage}% capacity" | mail -s "Disk Space Alert" "$EMAIL"
fi
done
}
check_disk_usage
Exercise 10.2: Process Manager
Problem: List and manage processes by name.
Hint: Use pgrep and pkill or ps with grep.
Solution:
#!/bin/bash
list_processes() {
echo "=== Process List ==="
ps aux | head -1
ps aux | grep -E "$1" | grep -v grep
}
kill_processes() {
echo "Killing processes matching: $1"
pkill -f "$1" && echo "Done" || echo "No processes found"
}
while true; do
clear
echo "=== Process Manager ==="
echo "1) List all processes"
echo "2) Search processes by name"
echo "3) Kill processes by name"
echo "4) Show process tree"
echo "5) Exit"
read -p "Choice: " choice
case $choice in
1)
ps aux | head -20
read -p "Press Enter to continue..."
;;
2)
read -p "Enter process name: " name
list_processes "$name"
read -p "Press Enter to continue..."
;;
3)
read -p "Enter process name to kill: " name
kill_processes "$name"
sleep 2
;;
4)
pstree | head -20
read -p "Press Enter to continue..."
;;
5)
exit 0
;;
esac
done
Exercise 10.3: User Management Script
Problem: Create a script to add/remove/list users.
Hint: Use useradd, userdel, and parse /etc/passwd.
Solution:
#!/bin/bash
# Must be run as root
if [ $EUID -ne 0 ]; then
echo "This script must be run as root"
exit 1
fi
list_users() {
echo "=== System Users ==="
awk -F: '{print $1 " (UID: " $3 ")"}' /etc/passwd | sort
}
add_user() {
read -p "Enter username: " username
read -s -p "Enter password: " password
echo
useradd -m -s /bin/bash "$username"
echo "$username:$password" | chpasswd
echo "User $username created successfully"
}
delete_user() {
read -p "Enter username to delete: " username
read -p "Remove home directory? (y/n): " remove_home
if [ "$remove_home" = "y" ]; then
userdel -r "$username"
else
userdel "$username"
fi
echo "User $username deleted"
}
while true; do
clear
echo "=== User Management ==="
echo "1) List users"
echo "2) Add user"
echo "3) Delete user"
echo "4) Exit"
read -p "Choice: " choice
case $choice in
1) list_users; read -p "Press Enter..." ;;
2) add_user; read -p "Press Enter..." ;;
3) delete_user; read -p "Press Enter..." ;;
4) exit 0 ;;
esac
done
Exercise 10.4: Service Monitor
Problem: Monitor system services and restart if down.
Hint: Use systemctl to check service status.
Solution:
#!/bin/bash
SERVICES=("nginx" "postgresql" "redis")
LOG_FILE="/var/log/service_monitor.log"
check_service() {
local service=$1
if systemctl is-active --quiet "$service"; then
echo "$(date): $service is running" >> "$LOG_FILE"
return 0
else
echo "$(date): $service is DOWN!" >> "$LOG_FILE"
return 1
fi
}
restart_service() {
local service=$1
echo "$(date): Attempting to restart $service" >> "$LOG_FILE"
systemctl restart "$service"
sleep 5
if systemctl is-active --quiet "$service"; then
echo "$(date): $service successfully restarted" >> "$LOG_FILE"
return 0
else
echo "$(date): Failed to restart $service" >> "$LOG_FILE"
return 1
fi
}
while true; do
for service in "${SERVICES[@]}"; do
if ! check_service "$service"; then
restart_service "$service"
fi
done
sleep 60
done
Exercise 10.5: Log Rotator
Problem: Rotate log files when they exceed a size limit.
Hint: Check file size and move with timestamp.
Solution:
#!/bin/bash
LOG_DIR="/var/log/myapp"
MAX_SIZE=$((10 * 1024 * 1024)) # 10MB
RETENTION_DAYS=7
rotate_log() {
local logfile=$1
if [ ! -f "$logfile" ]; then
return
fi
size=$(stat -c%s "$logfile" 2>/dev/null || stat -f%z "$logfile" 2>/dev/null)
if [ $size -gt $MAX_SIZE ]; then
timestamp=$(date +%Y%m%d_%H%M%S)
rotated="${logfile}.$timestamp"
echo "Rotating $logfile (size: $size bytes)"
mv "$logfile" "$rotated"
gzip "$rotated"
touch "$logfile"
# Clean old logs
find "$LOG_DIR" -name "*.gz" -mtime +$RETENTION_DAYS -delete
fi
}
# Rotate all .log files in directory
for logfile in "$LOG_DIR"/*.log; do
rotate_log "$logfile"
done
11. Real-World Projects
Project 11.1: System Information Dashboard
Problem: Create a comprehensive system information dashboard.
Solution:
#!/bin/bash
show_header() {
clear
echo "══════════════════════════════════════════════════"
echo " SYSTEM INFORMATION DASHBOARD "
echo "══════════════════════════════════════════════════"
echo "Hostname: $(hostname)"
echo "User: $(whoami)"
echo "Date: $(date '+%Y-%m-%d %H:%M:%S')"
echo "Uptime: $(uptime | awk '{print $3,$4}' | sed 's/,//')"
echo "══════════════════════════════════════════════════"
}
show_cpu() {
echo
echo "─── CPU Information ──────────────────────────────"
echo "Model: $(lscpu | grep "Model name" | cut -d: -f2 | xargs)"
echo "Cores: $(nproc)"
echo "Load Average: $(uptime | awk -F'load average:' '{print $2}')"
echo "Current Usage: $(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)%"
}
show_memory() {
echo
echo "─── Memory Information ───────────────────────────"
free -h | awk '
NR==1 {printf "%-10s %10s %10s %10s %10s\n", $1, $2, $3, $4, $6}
NR==2 {printf "%-10s %10s %10s %10s %10s\n", $1, $2, $3, $4, $6}
NR==3 {printf "%-10s %10s %10s %10s %10s\n", $1, $2, $3, $4, $5}
'
}
show_disk() {
echo
echo "─── Disk Usage ───────────────────────────────────"
df -h | grep -vE '^Filesystem|tmpfs|cdrom' | awk '{printf "%-20s %10s %10s %10s %s\n", $1, $2, $3, $4, $6}'
}
show_network() {
echo
echo "─── Network Information ──────────────────────────"
ip -4 addr show | grep inet | awk '{print "IP: " $2}' | sed 's/\/.*//'
echo "Default Gateway: $(ip route | grep default | awk '{print $3}')"
echo "DNS Servers: $(grep nameserver /etc/resolv.conf | awk '{print $2}' | tr '\n' ' ')"
}
show_processes() {
echo
echo "─── Top Processes by CPU ─────────────────────────"
ps aux --sort=-%cpu | head -6 | awk '{printf "%-10s %-10s %5s%% %5s%% %s\n", $1, $2, $3, $4, $11}'
}
show_services() {
echo
echo "─── Critical Services ────────────────────────────"
for service in sshd cron rsyslog; do
if systemctl is-active --quiet "$service"; then
echo "✓ $service: running"
else
echo "✗ $service: stopped"
fi
done
}
# Main loop
while true; do
show_header
show_cpu
show_memory
show_disk
show_network
show_processes
show_services
echo
echo "══════════════════════════════════════════════════"
echo "Press Ctrl+C to exit. Refreshing in 5 seconds..."
sleep 5
done
Project 11.2: Backup System with Rotation
Problem: Create a comprehensive backup system with rotation and logging.
Solution:
#!/bin/bash
# backup.sh - Automated backup system
# Configuration
BACKUP_DIR="/backup"
SOURCE_DIRS=("/home" "/etc" "/var/log")
EXCLUDE=("*.tmp" "*.cache" ".git")
RETENTION_DAYS=7
LOG_FILE="/var/log/backup.log"
EMAIL="admin@localhost"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
error() {
echo -e "${RED}[ERROR]${NC} $1"
log "ERROR: $1"
}
success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
log "SUCCESS: $1"
}
warn() {
echo -e "${YELLOW}[WARNING]${NC} $1"
log "WARNING: $1"
}
check_prerequisites() {
log "Checking prerequisites..."
# Check if running as root
if [ $EUID -ne 0 ]; then
error "This script must be run as root"
exit 1
fi
# Check backup directory
if [ ! -d "$BACKUP_DIR" ]; then
mkdir -p "$BACKUP_DIR"
success "Created backup directory: $BACKUP_DIR"
fi
# Check available space
available=$(df "$BACKUP_DIR" | awk 'NR==2 {print $4}')
if [ $available -lt 1048576 ]; then # 1GB in KB
warn "Low disk space on backup destination"
fi
}
create_backup() {
local timestamp=$(date +%Y%m%d_%H%M%S)
local backup_file="$BACKUP_DIR/backup_$timestamp.tar.gz"
local file_list="$BACKUP_DIR/filelist_$timestamp.txt"
log "Starting backup: $backup_file"
# Build exclude options
exclude_opts=()
for pattern in "${EXCLUDE[@]}"; do
exclude_opts+=("--exclude=$pattern")
done
# Create file list
find "${SOURCE_DIRS[@]}" -type f > "$file_list"
file_count=$(wc -l < "$file_list")
log "Found $file_count files to backup"
# Create backup with progress
tar -czf "$backup_file" "${exclude_opts[@]}" "${SOURCE_DIRS[@]}" 2>&1 | \
while read line; do
echo -n "."
done
echo
if [ $? -eq 0 ]; then
size=$(du -h "$backup_file" | cut -f1)
success "Backup created: $backup_file ($size)"
# Create checksum
md5sum "$backup_file" > "${backup_file}.md5"
# Log backup info
{
echo "Backup: $backup_file"
echo "Date: $(date)"
echo "Size: $size"
echo "Files: $file_count"
echo "Directories: ${SOURCE_DIRS[*]}"
echo "---"
} >> "$BACKUP_DIR/backup_history.log"
else
error "Backup failed"
rm -f "$backup_file"
return 1
fi
}
rotate_backups() {
log "Rotating old backups..."
cd "$BACKUP_DIR" || return
# Delete backups older than retention period
old_backups=$(find . -name "backup_*.tar.gz" -type f -mtime +$RETENTION_DAYS)
if [ -n "$old_backups" ]; then
echo "$old_backups" | while read backup; do
rm -f "$backup" "${backup}.md5"
log "Deleted old backup: $backup"
done
success "Old backups rotated"
else
log "No backups to rotate"
fi
}
verify_backups() {
log "Verifying recent backups..."
# Check last 5 backups
for backup in $(ls -t "$BACKUP_DIR"/backup_*.tar.gz 2>/dev/null | head -5); do
if [ -f "${backup}.md5" ]; then
if md5sum -c "${backup}.md5" >/dev/null 2>&1; then
success "Verified: $(basename "$backup")"
else
error "Checksum failed: $(basename "$backup")"
fi
fi
done
}
send_report() {
local report="/tmp/backup_report_$$.txt"
{
echo "Backup Report - $(date)"
echo "========================"
echo
echo "Status: $1"
echo
echo "Last 10 backups:"
ls -lh "$BACKUP_DIR"/backup_*.tar.gz 2>/dev/null | head -10
echo
echo "Backup directory usage:"
df -h "$BACKUP_DIR"
echo
echo "Log tail:"
tail -20 "$LOG_FILE"
} > "$report"
# mail -s "Backup Report" "$EMAIL" < "$report"
cat "$report"
rm "$report"
}
# Main execution
main() {
log "=== Backup Script Started ==="
check_prerequisites
if create_backup; then
rotate_backups
verify_backups
send_report "SUCCESS"
else
send_report "FAILED"
exit 1
fi
log "=== Backup Script Completed ==="
}
# Run main function
main
Project 11.3: Network Scanner
Problem: Create a network scanner to discover hosts and services.
Solution:
#!/bin/bash
# network_scanner.sh - Discover hosts and services on network
# Configuration
NETWORK="192.168.1"
TIMEOUT=1
THREADS=10
OUTPUT_DIR="/tmp/scan_results"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# Common ports to scan
declare -A PORTS
PORTS=(
[22]="SSH"
[80]="HTTP"
[443]="HTTPS"
[21]="FTP"
[25]="SMTP"
[3306]="MySQL"
[5432]="PostgreSQL"
[8080]="HTTP-Alt"
[8443]="HTTPS-Alt"
[3389]="RDP"
)
create_output_dir() {
mkdir -p "$OUTPUT_DIR"
rm -f "$OUTPUT_DIR"/host_*
}
ping_host() {
local ip=$1
if ping -c1 -W$TIMEOUT "$ip" >/dev/null 2>&1; then
echo "$ip" >> "$OUTPUT_DIR/live_hosts.txt"
echo -e "${GREEN}✓${NC} $ip is alive"
fi
}
scan_ports() {
local ip=$1
local host_file="$OUTPUT_DIR/host_$ip.txt"
for port in "${!PORTS[@]}"; do
( timeout $TIMEOUT bash -c "echo >/dev/tcp/$ip/$port" 2>/dev/null && \
echo "$port:${PORTS[$port]}" >> "$host_file" ) &
done
wait
if [ -s "$host_file" ]; then
echo -e "${BLUE} Services on $ip:${NC}"
while IFS=: read port service; do
echo " - $service ($port)"
done < "$host_file"
fi
}
discover_hosts() {
echo -e "${YELLOW}Discovering hosts on $NETWORK.0/24...${NC}"
# Ping scan with parallel processing
for i in {1..254}; do
ping_host "$NETWORK.$i" &
# Limit parallel processes
if [ $((i % THREADS)) -eq 0 ]; then
wait
fi
done
wait
echo -e "\n${GREEN}Found $(wc -l < "$OUTPUT_DIR/live_hosts.txt") live hosts${NC}"
}
scan_services() {
echo -e "\n${YELLOW}Scanning for services...${NC}"
while read ip; do
scan_ports "$ip" &
# Limit parallel scans
if [ $((++count % THREADS)) -eq 0 ]; then
wait
fi
done < "$OUTPUT_DIR/live_hosts.txt"
wait
}
generate_report() {
local report_file="$OUTPUT_DIR/network_report_$(date +%Y%m%d_%H%M%S).txt"
{
echo "Network Scan Report"
echo "==================="
echo "Scan Date: $(date)"
echo "Network: $NETWORK.0/24"
echo
echo "Live Hosts:"
echo "-----------"
while read ip; do
echo "$ip"
if [ -f "$OUTPUT_DIR/host_$ip.txt" ]; then
while IFS=: read port service; do
echo " └─ $service (port $port)"
done < "$OUTPUT_DIR/host_$ip.txt"
fi
echo
done < "$OUTPUT_DIR/live_hosts.txt"
echo "Summary:"
echo "--------"
echo "Total hosts found: $(wc -l < "$OUTPUT_DIR/live_hosts.txt")"
echo "Total services detected: $(cat "$OUTPUT_DIR"/host_* 2>/dev/null | wc -l)"
} > "$report_file"
echo -e "\n${GREEN}Report saved to: $report_file${NC}"
}
# Main execution
main() {
clear
echo "══════════════════════════════════════════"
echo " NETWORK SCANNER v1.0 "
echo "══════════════════════════════════════════"
create_output_dir
discover_hosts
scan_services
generate_report
echo -e "\n${GREEN}Scan complete!${NC}"
}
# Run main function
main
Project 11.4: Log Analyzer with Statistics
Problem: Create a comprehensive log analyzer with statistical reports.
Solution:
#!/bin/bash
# log_analyzer.sh - Comprehensive log file analyzer
# Configuration
LOG_FILE=""
REPORT_DIR="./reports"
VERBOSE=0
TOP_N=10
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
usage() {
echo "Usage: $0 [-f logfile] [-o output_dir] [-n top_n] [-v]"
echo " -f logfile Log file to analyze"
echo " -o output_dir Output directory for reports (default: ./reports)"
echo " -n top_n Number of top items to show (default: 10)"
echo " -v Verbose mode"
exit 1
}
while getopts "f:o:n:vh" opt; do
case $opt in
f) LOG_FILE="$OPTARG" ;;
o) REPORT_DIR="$OPTARG" ;;
n) TOP_N="$OPTARG" ;;
v) VERBOSE=1 ;;
h) usage ;;
*) usage ;;
esac
done
if [ -z "$LOG_FILE" ] || [ ! -f "$LOG_FILE" ]; then
echo -e "${RED}Error: Log file not specified or not found${NC}"
usage
fi
create_report_dir() {
mkdir -p "$REPORT_DIR"
echo -e "${GREEN}Report directory created: $REPORT_DIR${NC}"
}
analyze_basic_stats() {
local report="$REPORT_DIR/basic_stats.txt"
echo "Basic Statistics" > "$report"
echo "================" >> "$report"
echo "Log file: $LOG_FILE" >> "$report"
echo "Analysis date: $(date)" >> "$report"
echo >> "$report"
# Total lines
total_lines=$(wc -l < "$LOG_FILE")
echo "Total lines: $total_lines" >> "$report"
# Non-empty lines
non_empty=$(grep -c '.' "$LOG_FILE")
echo "Non-empty lines: $non_empty" >> "$report"
# Empty lines
empty=$((total_lines - non_empty))
echo "Empty lines: $empty" >> "$report"
# File size
size=$(du -h "$LOG_FILE" | cut -f1)
echo "File size: $size" >> "$report"
# Average line length
avg_length=$(awk '{sum+=length} END {print int(sum/NR)}' "$LOG_FILE")
echo "Average line length: $avg_length characters" >> "$report"
echo -e "${GREEN}Basic statistics saved to $report${NC}"
}
analyze_ip_addresses() {
local report="$REPORT_DIR/ip_addresses.txt"
echo "IP Address Analysis" > "$report"
echo "===================" >> "$report"
echo >> "$report"
# Extract and count IPs
grep -oE '\b([0-9]{1,3}\.){3}[0-9]{1,3}\b' "$LOG_FILE" | \
sort | uniq -c | sort -rn > "$report.tmp"
echo "Top $TOP_N IP addresses:" >> "$report"
head -$TOP_N "$report.tmp" >> "$report"
total_ips=$(wc -l < "$report.tmp")
echo >> "$report"
echo "Total unique IPs: $total_ips" >> "$report"
rm "$report.tmp"
echo -e "${GREEN}IP analysis saved to $report${NC}"
}
analyze_error_levels() {
local report="$REPORT_DIR/error_levels.txt"
echo "Error Level Analysis" > "$report"
echo "====================" >> "$report"
echo >> "$report"
# Common log levels
for level in ERROR WARN INFO DEBUG FATAL; do
count=$(grep -i "$level" "$LOG_FILE" | wc -l)
echo "$level: $count" >> "$report"
done
echo -e "${GREEN}Error level analysis saved to $report${NC}"
}
analyze_time_patterns() {
local report="$REPORT_DIR/time_patterns.txt"
echo "Time Pattern Analysis" > "$report"
echo "=====================" >> "$report"
echo >> "$report"
# Hourly distribution (assuming timestamp format HH:MM:SS)
grep -o ' [0-2][0-9]:[0-5][0-9]:[0-5][0-9]' "$LOG_FILE" | \
cut -d: -f1 | sort | uniq -c | sort -rn > "$report.tmp"
echo "Hourly distribution (top $TOP_N):" >> "$report"
head -$TOP_N "$report.tmp" | while read count hour; do
echo " Hour $hour: $count entries" >> "$report"
done
rm "$report.tmp"
# Daily distribution if available
grep -o '[0-9]{4}-[0-9]{2}-[0-9]{2}' "$LOG_FILE" | \
sort | uniq -c | sort -rn | head -$TOP_N > "$report.daily"
if [ -s "$report.daily" ]; then
echo >> "$report"
echo "Daily distribution:" >> "$report"
cat "$report.daily" | while read count date; do
echo " $date: $count entries" >> "$report"
done
fi
rm -f "$report.daily" "$report.tmp"
echo -e "${GREEN}Time pattern analysis saved to $report${NC}"
}
analyze_frequent_words() {
local report="$REPORT_DIR/frequent_words.txt"
echo "Frequent Word Analysis" > "$report"
echo "======================" >> "$report"
echo >> "$report"
# Extract common words (excluding common stop words)
tr '[:space:]' '\n' < "$LOG_FILE" | \
tr -d '[:punct:]' | \
tr '[:upper:]' '[:lower:]' | \
grep -E '^[a-z]{4,}$' | \
sort | uniq -c | sort -rn > "$report.tmp"
echo "Top $TOP_N most frequent words:" >> "$report"
head -$TOP_N "$report.tmp" | while read count word; do
echo " $word: $count occurrences" >> "$report"
done
rm "$report.tmp"
echo -e "${GREEN}Word frequency analysis saved to $report${NC}"
}
generate_html_report() {
local html_report="$REPORT_DIR/report.html"
cat > "$html_report" << EOF
<!DOCTYPE html>
<html>
<head>
<title>Log Analysis Report</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
h1 { color: #333; }
h2 { color: #666; border-bottom: 1px solid #ccc; }
.stats { background: #f5f5f5; padding: 10px; border-radius: 5px; }
table { border-collapse: collapse; width: 100%; }
th, td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; }
th { background-color: #4CAF50; color: white; }
tr:hover { background-color: #f5f5f5; }
</style>
</head>
<body>
<h1>Log Analysis Report</h1>
<p>Generated on: $(date)</p>
<p>Log file: $LOG_FILE</p>
<h2>Basic Statistics</h2>
<div class="stats">
EOF
cat "$REPORT_DIR/basic_stats.txt" | while read line; do
echo "<p>$line</p>" >> "$html_report"
done
cat >> "$html_report" << EOF
</div>
<h2>Top IP Addresses</h2>
<table>
<tr><th>Count</th><th>IP Address</th></tr>
EOF
grep -oE '\b([0-9]{1,3}\.){3}[0-9]{1,3}\b' "$LOG_FILE" | \
sort | uniq -c | sort -rn | head -$TOP_N | while read count ip; do
echo "<tr><td>$count</td><td>$ip</td></tr>" >> "$html_report"
done
cat >> "$html_report" << EOF
</table>
<h2>Error Levels</h2>
<table>
<tr><th>Level</th><th>Count</th></tr>
EOF
for level in ERROR WARN INFO DEBUG FATAL; do
count=$(grep -i "$level" "$LOG_FILE" | wc -l)
echo "<tr><td>$level</td><td>$count</td></tr>" >> "$html_report"
done
cat >> "$html_report" << EOF
</table>
</body>
</html>
EOF
echo -e "${GREEN}HTML report generated: $html_report${NC}"
}
# Main execution
main() {
clear
echo "══════════════════════════════════════════"
echo " LOG ANALYZER v1.0 "
echo "══════════════════════════════════════════"
echo "Analyzing: $LOG_FILE"
echo
create_report_dir
analyze_basic_stats
analyze_ip_addresses
analyze_error_levels
analyze_time_patterns
analyze_frequent_words
generate_html_report
echo -e "\n${GREEN}Analysis complete! Reports available in $REPORT_DIR${NC}"
if [ $VERBOSE -eq 1 ]; then
echo -e "\n${YELLOW}Report summary:${NC}"
ls -lh "$REPORT_DIR"
fi
}
main
Conclusion
These exercises cover a wide range of Bash scripting concepts:
Key Skills Developed
- Basic Syntax: Variables, conditionals, loops
- File Operations: Reading, writing, manipulating files
- Text Processing: grep, sed, awk, pattern matching
- Data Structures: Arrays, associative arrays
- Functions: Modular code, recursion, scope
- Error Handling: Exit codes, validation, logging
- System Administration: User management, process monitoring
- Network Programming: Connectivity testing, port scanning
- Automation: Backup systems, log analyzers
- Best Practices: Error handling, documentation, modularity
Tips for Success
- Start simple: Begin with basic exercises and gradually increase complexity
- Practice regularly: Consistent practice is key to mastery
- Read other scripts: Analyze well-written scripts to learn patterns
- Use shellcheck: Validate your scripts with shellcheck.net
- Comment your code: Document complex logic for future reference
- Test edge cases: Always test with unexpected inputs
- Version control: Use git to track your script evolution
These exercises provide a solid foundation for becoming proficient in Bash scripting. Modify and expand them to suit your specific needs and challenges.