Quick Recap of Part 1
Conditional Logic (if Statements)
Basic if
if-else
if-elif-else
Comparison Operators
Double Brackets [[ ]]
For Loops
Looping Through a List of Values
Looping Through a Number Range
C-Style For Loop
Looping Through Files
Practical Example: Batch Rename Files
While Loops
Basic While Loop
Reading a File Line by Line
Waiting for Something to Happen
Case Statements
The Problem case Solves
Practical Example: Script Menu
Exit Codes
Checking $?
Using Exit Codes in Conditionals
Setting Exit Codes in Your Scripts
Functions in Bash
Defining and Calling a Function
A More Realistic Function
local Variables
Functions with Return Values
Real Automation Script: System Health Check
Best Practices for Shell Scripts
Always Quote Your Variables
Use Meaningful Names
Use set -e and set -u at the Top
Use ShellCheck
Add a Usage Comment at the Top
Redirect Error Messages to stderr
Final Thoughts Series: Shell Scripting for Beginners | Part: 2 of 2
Level: Beginner–Intermediate | Time to read: ~18 minutes
Part 1: Shell Scripting for Beginners: From Zero to Automating Your First Tasks In Part 1, we covered the building blocks: If any of that sounds unfamiliar, go through Part 1 first — everything in this article builds on it. In Part 2, we're adding the logic layer. By the end of this, your scripts will be able to make decisions, repeat tasks, handle errors, and be structured enough that you'd actually want to maintain them. A script that just runs commands top to bottom is useful. A script that can decide what to do based on conditions is powerful. The structure is always: The spaces inside [ ] are not optional. [$age -ge 18] will fail. Always write [ $age -ge 18 ] with spaces. Bash uses different operators for numbers and strings — this trips people up constantly. For files and directories: Tip: Always quote your variables inside [ ]. Write [ "$name" = "Malhar" ], not [ $name = "Malhar" ]. If $name is empty or has spaces, the unquoted version breaks. Bash also supports [[ ]] — a more modern, forgiving syntax that handles edge cases better: [[ ]] also supports && and || directly inside it, while [ ] doesn't. Once you're comfortable with the basics, prefer [[ ]] for new scripts. When you need to repeat something a fixed number of times or iterate over a list — that's a for loop. This is where for loops get genuinely useful: Tip: Always test loops with echo first before running destructive commands like mv, rm, or cp. Replace the actual command with echo "would run: mv $file ..." and verify the output looks right. while loops keep running as long as a condition is true. They're perfect when you don't know in advance how many iterations you need. One of the most common uses of while in real scripts: IFS= prevents leading/trailing whitespace from being stripped. -r prevents backslashes from being interpreted. Both are good habits. This kind of loop is legitimately useful in deployment scripts where you need to wait for a service to start before proceeding. Tip: Always make sure your while loop has a way to end. A missing increment or a condition that never becomes false will give you an infinite loop that you'll have to kill with Ctrl+C. When you have one variable and multiple possible values to check against, case is far cleaner than a chain of if-elif statements. Imagine checking what day of the week it is: Each pattern ends with ), the commands end with ;;, and *) is the catch-all default (like else). The block ends with esac — that's case backwards, a classic Unix move. This kind of menu-driven script is a pattern you'll see all over real DevOps tooling. Every command you run in a terminal exits with a number. That number is the exit code, and it tells you whether the command succeeded or failed. $? holds the exit code of the last command that ran: Or more concisely — you can use the command directly in the if: When writing scripts that other scripts or tools will call, always set meaningful exit codes: >&2 redirects error messages to stderr instead of stdout — the right place for errors. exit 1 signals failure to whatever called your script. Tip: In CI/CD pipelines, exit codes are everything. A script that exits with 0 tells the pipeline "all good, move on." A non-zero exit code stops the pipeline and flags the step as failed. Always think about your exit codes when writing automation scripts. Once your scripts get longer than 30-40 lines, repeating the same blocks of code starts to hurt. Functions let you name a block of code and call it whenever you need it. The function is defined first, then called by name. Arguments passed to the function are available as $1, $2, etc. — just like script arguments, but scoped to the function. This log function is something you'd actually copy into real scripts. Notice the local keyword. Variables inside functions are global by default in Bash, which can cause bugs in longer scripts. Declaring them with local keeps them scoped to the function: Bash functions don't return values the way other languages do. They return exit codes (0-255). To "return" a value, you either echo it and capture it with $(), or use a global variable: Let's put everything together — conditionals, loops, functions, exit codes — into something you'd actually run on a server. This script performs a basic system health check and prints a summary report. Make it executable and run it: This is the kind of script that runs daily via cron on real servers. Add an email or Slack notification at the end and you have a basic monitoring system. Writing a script that works is one thing. Writing a script that someone else (or future you) can read, debug, and maintain is another. This is the single most common source of bugs in shell scripts. Filenames, paths, and user input can all contain spaces. Always quote. set -e means your script won't silently continue after a failed command. set -u catches typos in variable names. Add these to every script you write seriously. ShellCheck is a free static analysis tool for shell scripts. Paste your script in and it'll catch bugs, bad practices, and portability issues before you even run the script. You can also install it locally: It's the bash equivalent of a linter. Use it. Takes 30 seconds and saves a lot of confusion when you revisit the script months later. This means error messages won't pollute the stdout output of your script, which matters when other scripts or pipelines consume your script's output. If you've made it through both parts of this series, you can now write scripts that: That's not beginner knowledge anymore. For DevOps, shell scripting is the connective tissue between tools. It's how you glue together Docker commands, AWS CLI calls, database backups, log rotations, and deployment steps into a single automated workflow. Even if you work primarily with Python or Go for automation eventually, knowing how to read and write Bash means you can work on any server, in any environment, without needing anything installed. The best way to get better at this is to find something you do manually and automate it. Clean up old files? Script it. SSH into a server and run the same three commands? Script it. Check if your services are running? You just wrote that script. Start small. The scripts you write for yourself are the ones you'll actually learn from. That's a wrap on this series. If something wasn't clear or you ran into an issue, drop a comment — happy to help debug. And if you're building something interesting with what you learned, share it. Part 1 is here if you missed it: Shell Scripting for Beginners — Part 1 Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse
#!/bin/bash age=20 if [ $age -ge 18 ]; then echo "You're an adult."
fi
#!/bin/bash age=20 if [ $age -ge 18 ]; then echo "You're an adult."
fi
#!/bin/bash age=20 if [ $age -ge 18 ]; then echo "You're an adult."
fi
if [ condition ]; then # commands to run if condition is true
fi
if [ condition ]; then # commands to run if condition is true
fi
if [ condition ]; then # commands to run if condition is true
fi
#!/bin/bash read -p "Enter your age: " age if [ $age -ge 18 ]; then echo "Access granted."
else echo "Access denied. Come back in a few years."
fi
#!/bin/bash read -p "Enter your age: " age if [ $age -ge 18 ]; then echo "Access granted."
else echo "Access denied. Come back in a few years."
fi
#!/bin/bash read -p "Enter your age: " age if [ $age -ge 18 ]; then echo "Access granted."
else echo "Access denied. Come back in a few years."
fi
#!/bin/bash read -p "Enter your score: " score if [ $score -ge 90 ]; then echo "Grade: A"
elif [ $score -ge 75 ]; then echo "Grade: B"
elif [ $score -ge 60 ]; then echo "Grade: C"
else echo "Grade: F — study harder."
fi
#!/bin/bash read -p "Enter your score: " score if [ $score -ge 90 ]; then echo "Grade: A"
elif [ $score -ge 75 ]; then echo "Grade: B"
elif [ $score -ge 60 ]; then echo "Grade: C"
else echo "Grade: F — study harder."
fi
#!/bin/bash read -p "Enter your score: " score if [ $score -ge 90 ]; then echo "Grade: A"
elif [ $score -ge 75 ]; then echo "Grade: B"
elif [ $score -ge 60 ]; then echo "Grade: C"
else echo "Grade: F — study harder."
fi
#!/bin/bash name="Malhar" if [ "$name" = "Malhar" ]; then echo "Hey, I know you."
fi if [ -z "$name" ]; then echo "Name is empty."
else echo "Name is: $name"
fi
#!/bin/bash name="Malhar" if [ "$name" = "Malhar" ]; then echo "Hey, I know you."
fi if [ -z "$name" ]; then echo "Name is empty."
else echo "Name is: $name"
fi
#!/bin/bash name="Malhar" if [ "$name" = "Malhar" ]; then echo "Hey, I know you."
fi if [ -z "$name" ]; then echo "Name is empty."
else echo "Name is: $name"
fi
#!/bin/bash if [ -f "/etc/passwd" ]; then echo "File exists."
fi if [ -d "/tmp" ]; then echo "/tmp directory exists."
fi
#!/bin/bash if [ -f "/etc/passwd" ]; then echo "File exists."
fi if [ -d "/tmp" ]; then echo "/tmp directory exists."
fi
#!/bin/bash if [ -f "/etc/passwd" ]; then echo "File exists."
fi if [ -d "/tmp" ]; then echo "/tmp directory exists."
fi
#!/bin/bash name="Malhar Gupte" # This works safely with spaces in the variable
if [[ $name == *"Malhar"* ]]; then echo "Name contains Malhar"
fi
#!/bin/bash name="Malhar Gupte" # This works safely with spaces in the variable
if [[ $name == *"Malhar"* ]]; then echo "Name contains Malhar"
fi
#!/bin/bash name="Malhar Gupte" # This works safely with spaces in the variable
if [[ $name == *"Malhar"* ]]; then echo "Name contains Malhar"
fi
#!/bin/bash for language in Python Bash Java Go Rust; do echo "Language: $language"
done
#!/bin/bash for language in Python Bash Java Go Rust; do echo "Language: $language"
done
#!/bin/bash for language in Python Bash Java Go Rust; do echo "Language: $language"
done
Language: Python
Language: Bash
Language: Java
Language: Go
Language: Rust
Language: Python
Language: Bash
Language: Java
Language: Go
Language: Rust
Language: Python
Language: Bash
Language: Java
Language: Go
Language: Rust
#!/bin/bash for i in {1..5}; do echo "Iteration: $i"
done
#!/bin/bash for i in {1..5}; do echo "Iteration: $i"
done
#!/bin/bash for i in {1..5}; do echo "Iteration: $i"
done
for i in {0..20..5}; do echo "$i" # 0, 5, 10, 15, 20
done
for i in {0..20..5}; do echo "$i" # 0, 5, 10, 15, 20
done
for i in {0..20..5}; do echo "$i" # 0, 5, 10, 15, 20
done
#!/bin/bash for ((i=1; i<=5; i++)); do echo "Count: $i"
done
#!/bin/bash for ((i=1; i<=5; i++)); do echo "Count: $i"
done
#!/bin/bash for ((i=1; i<=5; i++)); do echo "Count: $i"
done
#!/bin/bash # Loop through all .log files in a directory
for file in /var/log/*.log; do echo "Processing: $file" echo "Size: $(du -sh "$file" | cut -f1)"
done
#!/bin/bash # Loop through all .log files in a directory
for file in /var/log/*.log; do echo "Processing: $file" echo "Size: $(du -sh "$file" | cut -f1)"
done
#!/bin/bash # Loop through all .log files in a directory
for file in /var/log/*.log; do echo "Processing: $file" echo "Size: $(du -sh "$file" | cut -f1)"
done
#!/bin/bash # Add a "backup_" prefix to all .txt files in current directory
for file in *.txt; do mv "$file" "backup_${file}" echo "Renamed: $file → backup_${file}"
done
#!/bin/bash # Add a "backup_" prefix to all .txt files in current directory
for file in *.txt; do mv "$file" "backup_${file}" echo "Renamed: $file → backup_${file}"
done
#!/bin/bash # Add a "backup_" prefix to all .txt files in current directory
for file in *.txt; do mv "$file" "backup_${file}" echo "Renamed: $file → backup_${file}"
done
#!/bin/bash count=1 while [ $count -le 5 ]; do echo "Count: $count" ((count++))
done
#!/bin/bash count=1 while [ $count -le 5 ]; do echo "Count: $count" ((count++))
done
#!/bin/bash count=1 while [ $count -le 5 ]; do echo "Count: $count" ((count++))
done
Count: 1
Count: 2
Count: 3
Count: 4
Count: 5
Count: 1
Count: 2
Count: 3
Count: 4
Count: 5
Count: 1
Count: 2
Count: 3
Count: 4
Count: 5
#!/bin/bash while IFS= read -r line; do echo "Line: $line"
done < /etc/hosts
#!/bin/bash while IFS= read -r line; do echo "Line: $line"
done < /etc/hosts
#!/bin/bash while IFS= read -r line; do echo "Line: $line"
done < /etc/hosts
#!/bin/bash echo "Waiting for server to come online..." while ! curl -s http://localhost:8080 > /dev/null; do echo "Not ready yet. Retrying in 3 seconds..." sleep 3
done echo "Server is up!"
#!/bin/bash echo "Waiting for server to come online..." while ! curl -s http://localhost:8080 > /dev/null; do echo "Not ready yet. Retrying in 3 seconds..." sleep 3
done echo "Server is up!"
#!/bin/bash echo "Waiting for server to come online..." while ! curl -s http://localhost:8080 > /dev/null; do echo "Not ready yet. Retrying in 3 seconds..." sleep 3
done echo "Server is up!"
# Verbose and repetitive with if-elif
if [ "$day" = "Monday" ]; then ...
elif [ "$day" = "Tuesday" ]; then ...
elif [ "$day" = "Wednesday" ]; then ...
# Verbose and repetitive with if-elif
if [ "$day" = "Monday" ]; then ...
elif [ "$day" = "Tuesday" ]; then ...
elif [ "$day" = "Wednesday" ]; then ...
# Verbose and repetitive with if-elif
if [ "$day" = "Monday" ]; then ...
elif [ "$day" = "Tuesday" ]; then ...
elif [ "$day" = "Wednesday" ]; then ...
#!/bin/bash read -p "Enter day: " day case $day in Monday) echo "Start of the week. Let's go." ;; Tuesday | Wednesday | Thursday) echo "Mid-week grind." ;; Friday) echo "Almost there." ;; Saturday | Sunday) echo "Weekend. Close the laptop." ;; *) echo "That's not a valid day." ;;
esac
#!/bin/bash read -p "Enter day: " day case $day in Monday) echo "Start of the week. Let's go." ;; Tuesday | Wednesday | Thursday) echo "Mid-week grind." ;; Friday) echo "Almost there." ;; Saturday | Sunday) echo "Weekend. Close the laptop." ;; *) echo "That's not a valid day." ;;
esac
#!/bin/bash read -p "Enter day: " day case $day in Monday) echo "Start of the week. Let's go." ;; Tuesday | Wednesday | Thursday) echo "Mid-week grind." ;; Friday) echo "Almost there." ;; Saturday | Sunday) echo "Weekend. Close the laptop." ;; *) echo "That's not a valid day." ;;
esac
#!/bin/bash echo "==============================="
echo " Server Management Menu"
echo "==============================="
echo "1) Check disk usage"
echo "2) Check memory usage"
echo "3) List running processes"
echo "4) Exit"
echo "" read -p "Choose an option: " option case $option in 1) df -h ;; 2) free -h ;; 3) ps aux | head -20 ;; 4) echo "Goodbye." exit 0 ;; *) echo "Invalid option." ;;
esac
#!/bin/bash echo "==============================="
echo " Server Management Menu"
echo "==============================="
echo "1) Check disk usage"
echo "2) Check memory usage"
echo "3) List running processes"
echo "4) Exit"
echo "" read -p "Choose an option: " option case $option in 1) df -h ;; 2) free -h ;; 3) ps aux | head -20 ;; 4) echo "Goodbye." exit 0 ;; *) echo "Invalid option." ;;
esac
#!/bin/bash echo "==============================="
echo " Server Management Menu"
echo "==============================="
echo "1) Check disk usage"
echo "2) Check memory usage"
echo "3) List running processes"
echo "4) Exit"
echo "" read -p "Choose an option: " option case $option in 1) df -h ;; 2) free -h ;; 3) ps aux | head -20 ;; 4) echo "Goodbye." exit 0 ;; *) echo "Invalid option." ;;
esac
#!/bin/bash ls /tmp
echo "Exit code: $?" # 0 — /tmp exists ls /nonexistent_directory
echo "Exit code: $?" # 2 — directory not found
#!/bin/bash ls /tmp
echo "Exit code: $?" # 0 — /tmp exists ls /nonexistent_directory
echo "Exit code: $?" # 2 — directory not found
#!/bin/bash ls /tmp
echo "Exit code: $?" # 0 — /tmp exists ls /nonexistent_directory
echo "Exit code: $?" # 2 — directory not found
#!/bin/bash ping -c 1 google.com > /dev/null 2>&1 if [ $? -eq 0 ]; then echo "Internet is up."
else echo "No internet connection."
fi
#!/bin/bash ping -c 1 google.com > /dev/null 2>&1 if [ $? -eq 0 ]; then echo "Internet is up."
else echo "No internet connection."
fi
#!/bin/bash ping -c 1 google.com > /dev/null 2>&1 if [ $? -eq 0 ]; then echo "Internet is up."
else echo "No internet connection."
fi
#!/bin/bash if ping -c 1 google.com > /dev/null 2>&1; then echo "Internet is up."
else echo "No internet connection."
fi
#!/bin/bash if ping -c 1 google.com > /dev/null 2>&1; then echo "Internet is up."
else echo "No internet connection."
fi
#!/bin/bash if ping -c 1 google.com > /dev/null 2>&1; then echo "Internet is up."
else echo "No internet connection."
fi
#!/bin/bash backup_file="/backups/db.sql" if [ ! -f "$backup_file" ]; then echo "ERROR: Backup file not found." >&2 exit 1
fi echo "Backup file found. Proceeding..."
exit 0
#!/bin/bash backup_file="/backups/db.sql" if [ ! -f "$backup_file" ]; then echo "ERROR: Backup file not found." >&2 exit 1
fi echo "Backup file found. Proceeding..."
exit 0
#!/bin/bash backup_file="/backups/db.sql" if [ ! -f "$backup_file" ]; then echo "ERROR: Backup file not found." >&2 exit 1
fi echo "Backup file found. Proceeding..."
exit 0
#!/bin/bash greet() { echo "Hello, $1!"
} greet "Malhar"
greet "World"
#!/bin/bash greet() { echo "Hello, $1!"
} greet "Malhar"
greet "World"
#!/bin/bash greet() { echo "Hello, $1!"
} greet "Malhar"
greet "World"
Hello, Malhar!
Hello, World!
Hello, Malhar!
Hello, World!
Hello, Malhar!
Hello, World!
#!/bin/bash log() { local level=$1 local message=$2 local timestamp=$(date "+%Y-%m-%d %H:%M:%S") echo "[$timestamp] [$level] $message"
} log "INFO" "Script started."
log "INFO" "Checking disk usage..."
log "WARN" "Disk usage above 80%."
log "ERROR" "Backup directory not found."
#!/bin/bash log() { local level=$1 local message=$2 local timestamp=$(date "+%Y-%m-%d %H:%M:%S") echo "[$timestamp] [$level] $message"
} log "INFO" "Script started."
log "INFO" "Checking disk usage..."
log "WARN" "Disk usage above 80%."
log "ERROR" "Backup directory not found."
#!/bin/bash log() { local level=$1 local message=$2 local timestamp=$(date "+%Y-%m-%d %H:%M:%S") echo "[$timestamp] [$level] $message"
} log "INFO" "Script started."
log "INFO" "Checking disk usage..."
log "WARN" "Disk usage above 80%."
log "ERROR" "Backup directory not found."
[2025-03-10 10:42:01] [INFO] Script started.
[2025-03-10 10:42:01] [INFO] Checking disk usage...
[2025-03-10 10:42:01] [WARN] Disk usage above 80%.
[2025-03-10 10:42:01] [ERROR] Backup directory not found.
[2025-03-10 10:42:01] [INFO] Script started.
[2025-03-10 10:42:01] [INFO] Checking disk usage...
[2025-03-10 10:42:01] [WARN] Disk usage above 80%.
[2025-03-10 10:42:01] [ERROR] Backup directory not found.
[2025-03-10 10:42:01] [INFO] Script started.
[2025-03-10 10:42:01] [INFO] Checking disk usage...
[2025-03-10 10:42:01] [WARN] Disk usage above 80%.
[2025-03-10 10:42:01] [ERROR] Backup directory not found.
#!/bin/bash count=10 # global increment() { local count=0 # local — doesn't touch the global $count ((count++)) echo "Inside function: $count"
} increment
echo "Outside function: $count"
#!/bin/bash count=10 # global increment() { local count=0 # local — doesn't touch the global $count ((count++)) echo "Inside function: $count"
} increment
echo "Outside function: $count"
#!/bin/bash count=10 # global increment() { local count=0 # local — doesn't touch the global $count ((count++)) echo "Inside function: $count"
} increment
echo "Outside function: $count"
Inside function: 1
Outside function: 10
Inside function: 1
Outside function: 10
Inside function: 1
Outside function: 10
#!/bin/bash get_disk_usage() { local path=${1:-"/"} df -h "$path" | awk 'NR==2 {print $5}' | tr -d '%'
} usage=$(get_disk_usage "/")
echo "Disk usage on /: ${usage}%" if [ "$usage" -gt 80 ]; then echo "Warning: disk usage is high."
fi
#!/bin/bash get_disk_usage() { local path=${1:-"/"} df -h "$path" | awk 'NR==2 {print $5}' | tr -d '%'
} usage=$(get_disk_usage "/")
echo "Disk usage on /: ${usage}%" if [ "$usage" -gt 80 ]; then echo "Warning: disk usage is high."
fi
#!/bin/bash get_disk_usage() { local path=${1:-"/"} df -h "$path" | awk 'NR==2 {print $5}' | tr -d '%'
} usage=$(get_disk_usage "/")
echo "Disk usage on /: ${usage}%" if [ "$usage" -gt 80 ]; then echo "Warning: disk usage is high."
fi
#!/bin/bash # ─────────────────────────────────────────────────────
# system_health_check.sh
# Checks disk, memory, CPU load, and running services
# Usage: ./system_health_check.sh
# ───────────────────────────────────────────────────── # ── Thresholds ────────────────────────────────────────
DISK_THRESHOLD=80
MEM_THRESHOLD=85
SERVICES=("nginx" "ssh") # ── Colors (optional but makes output readable) ───────
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color (reset) # ── Logging Function ──────────────────────────────────
log() { local level=$1 local message=$2 local timestamp=$(date "+%Y-%m-%d %H:%M:%S") case $level in INFO) echo -e "${GREEN}[$timestamp] [INFO]${NC} $message" ;; WARN) echo -e "${YELLOW}[$timestamp] [WARN]${NC} $message" ;; ERROR) echo -e "${RED}[$timestamp] [ERROR]${NC} $message" ;; esac
} # ── Check Disk Usage ──────────────────────────────────
check_disk() { log "INFO" "Checking disk usage..." while IFS= read -r line; do usage=$(echo "$line" | awk '{print $5}' | tr -d '%') mount=$(echo "$line" | awk '{print $6}') if [ "$usage" -ge "$DISK_THRESHOLD" ]; then log "WARN" "Disk usage on $mount is at ${usage}% — above threshold of ${DISK_THRESHOLD}%" else log "INFO" "Disk usage on $mount: ${usage}% — OK" fi done < <(df -h | awk 'NR>1 && $1 ~ /^\/dev/')
} # ── Check Memory Usage ────────────────────────────────
check_memory() { log "INFO" "Checking memory usage..." local total=$(free | awk '/^Mem:/ {print $2}') local used=$(free | awk '/^Mem:/ {print $3}') local usage=$(( used * 100 / total )) if [ "$usage" -ge "$MEM_THRESHOLD" ]; then log "WARN" "Memory usage is at ${usage}% — above threshold of ${MEM_THRESHOLD}%" else log "INFO" "Memory usage: ${usage}% — OK" fi
} # ── Check CPU Load ────────────────────────────────────
check_cpu() { log "INFO" "Checking CPU load..." local load=$(uptime | awk -F'load average:' '{print $2}' | awk -F',' '{print $1}' | xargs) log "INFO" "1-minute load average: $load"
} # ── Check Services ────────────────────────────────────
check_services() { log "INFO" "Checking critical services..." for service in "${SERVICES[@]}"; do if systemctl is-active --quiet "$service" 2>/dev/null; then log "INFO" "Service [$service] is running — OK" else log "ERROR" "Service [$service] is NOT running!" fi done
} # ── Main ──────────────────────────────────────────────
main() { echo "" echo "==============================================" echo " System Health Check" echo " $(date)" echo " Host: $(hostname)" echo "==============================================" echo "" check_disk echo "" check_memory echo "" check_cpu echo "" check_services echo "" echo "==============================================" echo " Health check complete." echo "==============================================" echo ""
} main
exit 0
#!/bin/bash # ─────────────────────────────────────────────────────
# system_health_check.sh
# Checks disk, memory, CPU load, and running services
# Usage: ./system_health_check.sh
# ───────────────────────────────────────────────────── # ── Thresholds ────────────────────────────────────────
DISK_THRESHOLD=80
MEM_THRESHOLD=85
SERVICES=("nginx" "ssh") # ── Colors (optional but makes output readable) ───────
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color (reset) # ── Logging Function ──────────────────────────────────
log() { local level=$1 local message=$2 local timestamp=$(date "+%Y-%m-%d %H:%M:%S") case $level in INFO) echo -e "${GREEN}[$timestamp] [INFO]${NC} $message" ;; WARN) echo -e "${YELLOW}[$timestamp] [WARN]${NC} $message" ;; ERROR) echo -e "${RED}[$timestamp] [ERROR]${NC} $message" ;; esac
} # ── Check Disk Usage ──────────────────────────────────
check_disk() { log "INFO" "Checking disk usage..." while IFS= read -r line; do usage=$(echo "$line" | awk '{print $5}' | tr -d '%') mount=$(echo "$line" | awk '{print $6}') if [ "$usage" -ge "$DISK_THRESHOLD" ]; then log "WARN" "Disk usage on $mount is at ${usage}% — above threshold of ${DISK_THRESHOLD}%" else log "INFO" "Disk usage on $mount: ${usage}% — OK" fi done < <(df -h | awk 'NR>1 && $1 ~ /^\/dev/')
} # ── Check Memory Usage ────────────────────────────────
check_memory() { log "INFO" "Checking memory usage..." local total=$(free | awk '/^Mem:/ {print $2}') local used=$(free | awk '/^Mem:/ {print $3}') local usage=$(( used * 100 / total )) if [ "$usage" -ge "$MEM_THRESHOLD" ]; then log "WARN" "Memory usage is at ${usage}% — above threshold of ${MEM_THRESHOLD}%" else log "INFO" "Memory usage: ${usage}% — OK" fi
} # ── Check CPU Load ────────────────────────────────────
check_cpu() { log "INFO" "Checking CPU load..." local load=$(uptime | awk -F'load average:' '{print $2}' | awk -F',' '{print $1}' | xargs) log "INFO" "1-minute load average: $load"
} # ── Check Services ────────────────────────────────────
check_services() { log "INFO" "Checking critical services..." for service in "${SERVICES[@]}"; do if systemctl is-active --quiet "$service" 2>/dev/null; then log "INFO" "Service [$service] is running — OK" else log "ERROR" "Service [$service] is NOT running!" fi done
} # ── Main ──────────────────────────────────────────────
main() { echo "" echo "==============================================" echo " System Health Check" echo " $(date)" echo " Host: $(hostname)" echo "==============================================" echo "" check_disk echo "" check_memory echo "" check_cpu echo "" check_services echo "" echo "==============================================" echo " Health check complete." echo "==============================================" echo ""
} main
exit 0
#!/bin/bash # ─────────────────────────────────────────────────────
# system_health_check.sh
# Checks disk, memory, CPU load, and running services
# Usage: ./system_health_check.sh
# ───────────────────────────────────────────────────── # ── Thresholds ────────────────────────────────────────
DISK_THRESHOLD=80
MEM_THRESHOLD=85
SERVICES=("nginx" "ssh") # ── Colors (optional but makes output readable) ───────
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color (reset) # ── Logging Function ──────────────────────────────────
log() { local level=$1 local message=$2 local timestamp=$(date "+%Y-%m-%d %H:%M:%S") case $level in INFO) echo -e "${GREEN}[$timestamp] [INFO]${NC} $message" ;; WARN) echo -e "${YELLOW}[$timestamp] [WARN]${NC} $message" ;; ERROR) echo -e "${RED}[$timestamp] [ERROR]${NC} $message" ;; esac
} # ── Check Disk Usage ──────────────────────────────────
check_disk() { log "INFO" "Checking disk usage..." while IFS= read -r line; do usage=$(echo "$line" | awk '{print $5}' | tr -d '%') mount=$(echo "$line" | awk '{print $6}') if [ "$usage" -ge "$DISK_THRESHOLD" ]; then log "WARN" "Disk usage on $mount is at ${usage}% — above threshold of ${DISK_THRESHOLD}%" else log "INFO" "Disk usage on $mount: ${usage}% — OK" fi done < <(df -h | awk 'NR>1 && $1 ~ /^\/dev/')
} # ── Check Memory Usage ────────────────────────────────
check_memory() { log "INFO" "Checking memory usage..." local total=$(free | awk '/^Mem:/ {print $2}') local used=$(free | awk '/^Mem:/ {print $3}') local usage=$(( used * 100 / total )) if [ "$usage" -ge "$MEM_THRESHOLD" ]; then log "WARN" "Memory usage is at ${usage}% — above threshold of ${MEM_THRESHOLD}%" else log "INFO" "Memory usage: ${usage}% — OK" fi
} # ── Check CPU Load ────────────────────────────────────
check_cpu() { log "INFO" "Checking CPU load..." local load=$(uptime | awk -F'load average:' '{print $2}' | awk -F',' '{print $1}' | xargs) log "INFO" "1-minute load average: $load"
} # ── Check Services ────────────────────────────────────
check_services() { log "INFO" "Checking critical services..." for service in "${SERVICES[@]}"; do if systemctl is-active --quiet "$service" 2>/dev/null; then log "INFO" "Service [$service] is running — OK" else log "ERROR" "Service [$service] is NOT running!" fi done
} # ── Main ──────────────────────────────────────────────
main() { echo "" echo "==============================================" echo " System Health Check" echo " $(date)" echo " Host: $(hostname)" echo "==============================================" echo "" check_disk echo "" check_memory echo "" check_cpu echo "" check_services echo "" echo "==============================================" echo " Health check complete." echo "==============================================" echo ""
} main
exit 0
chmod +x system_health_check.sh
./system_health_check.sh
chmod +x system_health_check.sh
./system_health_check.sh
chmod +x system_health_check.sh
./system_health_check.sh
============================================== System Health Check Mon Mar 10 10:45:01 IST 2025 Host: dev-server
============================================== [2025-03-10 10:45:01] [INFO] Checking disk usage...
[2025-03-10 10:45:01] [INFO] Disk usage on /: 43% — OK
[2025-03-10 10:45:01] [WARN] Disk usage on /data is at 87% — above threshold of 80% [2025-03-10 10:45:01] [INFO] Checking memory usage...
[2025-03-10 10:45:01] [INFO] Memory usage: 61% — OK [2025-03-10 10:45:01] [INFO] Checking CPU load...
[2025-03-10 10:45:01] [INFO] 1-minute load average: 0.42 [2025-03-10 10:45:01] [INFO] Checking critical services...
[2025-03-10 10:45:01] [INFO] Service [nginx] is running — OK
[2025-03-10 10:45:01] [ERROR] Service [ssh] is NOT running! ============================================== Health check complete.
==============================================
============================================== System Health Check Mon Mar 10 10:45:01 IST 2025 Host: dev-server
============================================== [2025-03-10 10:45:01] [INFO] Checking disk usage...
[2025-03-10 10:45:01] [INFO] Disk usage on /: 43% — OK
[2025-03-10 10:45:01] [WARN] Disk usage on /data is at 87% — above threshold of 80% [2025-03-10 10:45:01] [INFO] Checking memory usage...
[2025-03-10 10:45:01] [INFO] Memory usage: 61% — OK [2025-03-10 10:45:01] [INFO] Checking CPU load...
[2025-03-10 10:45:01] [INFO] 1-minute load average: 0.42 [2025-03-10 10:45:01] [INFO] Checking critical services...
[2025-03-10 10:45:01] [INFO] Service [nginx] is running — OK
[2025-03-10 10:45:01] [ERROR] Service [ssh] is NOT running! ============================================== Health check complete.
==============================================
============================================== System Health Check Mon Mar 10 10:45:01 IST 2025 Host: dev-server
============================================== [2025-03-10 10:45:01] [INFO] Checking disk usage...
[2025-03-10 10:45:01] [INFO] Disk usage on /: 43% — OK
[2025-03-10 10:45:01] [WARN] Disk usage on /data is at 87% — above threshold of 80% [2025-03-10 10:45:01] [INFO] Checking memory usage...
[2025-03-10 10:45:01] [INFO] Memory usage: 61% — OK [2025-03-10 10:45:01] [INFO] Checking CPU load...
[2025-03-10 10:45:01] [INFO] 1-minute load average: 0.42 [2025-03-10 10:45:01] [INFO] Checking critical services...
[2025-03-10 10:45:01] [INFO] Service [nginx] is running — OK
[2025-03-10 10:45:01] [ERROR] Service [ssh] is NOT running! ============================================== Health check complete.
==============================================
# Bad — breaks if filename has spaces
rm $filename # Good
rm "$filename"
# Bad — breaks if filename has spaces
rm $filename # Good
rm "$filename"
# Bad — breaks if filename has spaces
rm $filename # Good
rm "$filename"
# Hard to read
for f in *.log; do s=$(wc -l < "$f") echo "$f: $s"
done # Clear
for log_file in *.log; do line_count=$(wc -l < "$log_file") echo "$log_file: $line_count lines"
done
# Hard to read
for f in *.log; do s=$(wc -l < "$f") echo "$f: $s"
done # Clear
for log_file in *.log; do line_count=$(wc -l < "$log_file") echo "$log_file: $line_count lines"
done
# Hard to read
for f in *.log; do s=$(wc -l < "$f") echo "$f: $s"
done # Clear
for log_file in *.log; do line_count=$(wc -l < "$log_file") echo "$log_file: $line_count lines"
done
#!/bin/bash
set -e # Exit immediately if any command fails
set -u # Treat unset variables as errors
#!/bin/bash
set -e # Exit immediately if any command fails
set -u # Treat unset variables as errors
#!/bin/bash
set -e # Exit immediately if any command fails
set -u # Treat unset variables as errors
# Ubuntu/Debian
sudo apt install shellcheck # Then run it on your script
shellcheck your_script.sh
# Ubuntu/Debian
sudo apt install shellcheck # Then run it on your script
shellcheck your_script.sh
# Ubuntu/Debian
sudo apt install shellcheck # Then run it on your script
shellcheck your_script.sh
#!/bin/bash
# ─────────────────────────────────────────
# backup.sh
# Description: Backs up a directory to a target location
# Usage: ./backup.sh <source_dir> <target_dir>
# Author: Malhar Gupte
# Last updated: 2025-03-10
# ─────────────────────────────────────────
#!/bin/bash
# ─────────────────────────────────────────
# backup.sh
# Description: Backs up a directory to a target location
# Usage: ./backup.sh <source_dir> <target_dir>
# Author: Malhar Gupte
# Last updated: 2025-03-10
# ─────────────────────────────────────────
#!/bin/bash
# ─────────────────────────────────────────
# backup.sh
# Description: Backs up a directory to a target location
# Usage: ./backup.sh <source_dir> <target_dir>
# Author: Malhar Gupte
# Last updated: 2025-03-10
# ─────────────────────────────────────────
# Good practice for error messages
echo "ERROR: File not found." >&2
exit 1
# Good practice for error messages
echo "ERROR: File not found." >&2
exit 1
# Good practice for error messages
echo "ERROR: File not found." >&2
exit 1 - Writing and running your first script (chmod +x, ./script.sh)
- The shebang line (#!/bin/bash) and what it does
- Variables, curly brace syntax, and command substitution
- Passing arguments with $1, $2, $#
- Reading user input with read and read -p
- Arithmetic using $(( )) - 0 = success
- Any non-zero value = failure (the specific number often indicates the type of error) - Make decisions with if, elif, case
- Repeat tasks with for and while
- Handle success and failure with exit codes
- Stay organized with functions
- Check real system health on a live server