Complete Bash Exercises with Solutions

Table of Contents

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

  1. Basic Exercises
  2. Variables and Data Types
  3. Conditional Statements
  4. Loop Exercises
  5. Function Exercises
  6. File Operations
  7. Text Processing
  8. Arrays and Data Structures
  9. Advanced Scripting
  10. System Administration
  11. 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

  1. Basic Syntax: Variables, conditionals, loops
  2. File Operations: Reading, writing, manipulating files
  3. Text Processing: grep, sed, awk, pattern matching
  4. Data Structures: Arrays, associative arrays
  5. Functions: Modular code, recursion, scope
  6. Error Handling: Exit codes, validation, logging
  7. System Administration: User management, process monitoring
  8. Network Programming: Connectivity testing, port scanning
  9. Automation: Backup systems, log analyzers
  10. Best Practices: Error handling, documentation, modularity

Tips for Success

  1. Start simple: Begin with basic exercises and gradually increase complexity
  2. Practice regularly: Consistent practice is key to mastery
  3. Read other scripts: Analyze well-written scripts to learn patterns
  4. Use shellcheck: Validate your scripts with shellcheck.net
  5. Comment your code: Document complex logic for future reference
  6. Test edge cases: Always test with unexpected inputs
  7. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper