The goal is to document useful Bash scripting patterns that can be reused for troubleshooting, automation, health checks, log analysis, deployment validation, and daily Linux operations.
A basic Bash script usually starts with a shebang, variables, commands, and optional logic.
#!/bin/bash
MESSAGE="Hello from Bash"
echo "$MESSAGE"
Run the script:
chmod +x script.sh
./script.sh
Basic structure:
#!/bin/bash
# Variables
APP_NAME="my-app"
ENVIRONMENT="dev"
# Command output
echo "Starting script for $APP_NAME in $ENVIRONMENT"
# Logic
if [ "$ENVIRONMENT" = "dev" ]; then
echo "Running in development mode"
fi
Variables store values that can be reused in the script.
#!/bin/bash
APP_NAME="payment-service"
PORT=8080
ENVIRONMENT="production"
echo "Application: $APP_NAME"
echo "Port: $PORT"
echo "Environment: $ENVIRONMENT"
Use quotes around variables to avoid issues with spaces or empty values.
Good:
if [ "$APP_NAME" = "payment-service" ]; then
echo "App matched"
fi
Avoid:
if [ $APP_NAME = payment-service ]; then
echo "App matched"
fi
Why quotes matter:
APP_NAME="payment service"
if [ "$APP_NAME" = "payment service" ]; then
echo "App matched"
fi
Without quotes, values with spaces can break the script.
Arguments allow you to pass values to a script from the command line.
#!/bin/bash
echo "Script name: $0"
echo "First argument: $1"
echo "Second argument: $2"
echo "Number of arguments: $#"
Run:
./deploy.sh dev payment-service
Example output:
Script name: ./deploy.sh
First argument: dev
Second argument: payment-service
Number of arguments: 2
Common use:
#!/bin/bash
ENVIRONMENT=$1
APP_NAME=$2
echo "Deploying $APP_NAME to $ENVIRONMENT"
Common special variables:
$0 Script name
$1 First argument
$2 Second argument
$3 Third argument
$# Number of arguments
$? Exit code of the last command
$$ Process ID of the current script
$@ All arguments
Always validate input before running automation.
#!/bin/bash
if [ $# -lt 2 ]; then
echo "Usage: $0 <environment> <app-name>"
exit 1
fi
ENVIRONMENT=$1
APP_NAME=$2
echo "Deploying $APP_NAME to $ENVIRONMENT"
This prevents accidental or incomplete execution.
Example with allowed values:
#!/bin/bash
ENVIRONMENT=$1
if [ "$ENVIRONMENT" != "dev" ] && [ "$ENVIRONMENT" != "staging" ] && [ "$ENVIRONMENT" != "prod" ]; then
echo "Invalid environment. Use: dev, staging, or prod"
exit 1
fi
echo "Environment selected: $ENVIRONMENT"
Every command returns an exit code.
0 Success
Non-zero Failure
Check the last command with $?:
#!/bin/bash
systemctl restart nginx
if [ $? -eq 0 ]; then
echo "Nginx restarted successfully"
else
echo "Failed to restart Nginx"
exit 1
fi
Better style:
#!/bin/bash
if systemctl restart nginx; then
echo "Nginx restarted successfully"
else
echo "Failed to restart Nginx"
exit 1
fi
Why this matters in DevOps:
Conditions are used to make decisions inside scripts.
#!/bin/bash
ENVIRONMENT="prod"
if [ "$ENVIRONMENT" = "prod" ]; then
echo "Production environment"
else
echo "Non-production environment"
fi
#!/bin/bash
CPU_USAGE=85
if [ $CPU_USAGE -ge 90 ]; then
echo "Critical CPU usage"
elif [ $CPU_USAGE -ge 70 ]; then
echo "Warning: High CPU usage"
else
echo "CPU usage is normal"
fi
Common numeric operators:
-eq Equal
-ne Not equal
-gt Greater than
-ge Greater than or equal
-lt Less than
-le Less than or equal
#!/bin/bash
FILE="/var/log/syslog"
if [ -f "$FILE" ]; then
echo "File exists: $FILE"
else
echo "File not found: $FILE"
fi
Common file checks:
-f File exists and is a regular file
-d Directory exists
-r File is readable
-w File is writable
-x File is executable
Example checking if a directory exists:
#!/bin/bash
DIR="/var/log"
if [ -d "$DIR" ]; then
echo "Directory exists: $DIR"
else
echo "Directory not found: $DIR"
fi
Use case when there are multiple options.
#!/bin/bash
ACTION=$1
SERVICE=$2
case $ACTION in
start)
systemctl start "$SERVICE"
;;
stop)
systemctl stop "$SERVICE"
;;
restart)
systemctl restart "$SERVICE"
;;
status)
systemctl status "$SERVICE"
;;
*)
echo "Usage: $0 {start|stop|restart|status} <service>"
exit 1
;;
esac
Run:
./service-manager.sh restart nginx
Common use cases:
Loops repeat tasks.
#!/bin/bash
for SERVER in web01 web02 web03; do
echo "Checking server: $SERVER"
done
#!/bin/bash
for FILE in /var/log/*.log; do
echo "Processing $FILE"
done
#!/bin/bash
COUNT=1
while [ $COUNT -le 5 ]; do
echo "Attempt $COUNT"
COUNT=$((COUNT + 1))
done
#!/bin/bash
while true; do
date
uptime
sleep 10
done
Use infinite loops carefully.
break exits a loop immediately.
#!/bin/bash
for NUMBER in 1 2 3 4 5; do
if [ $NUMBER -eq 3 ]; then
break
fi
echo "$NUMBER"
done
continue skips the current iteration and moves to the next one.
#!/bin/bash
for NUMBER in 1 2 3 4 5; do
if [ $NUMBER -eq 3 ]; then
continue
fi
echo "$NUMBER"
done
Functions help organize reusable logic.
#!/bin/bash
log_info() {
echo "[INFO] $1"
}
log_error() {
echo "[ERROR] $1"
}
log_info "Starting deployment"
log_error "Deployment failed"
Example with service check:
#!/bin/bash
check_service() {
SERVICE=$1
if systemctl is-active --quiet "$SERVICE"; then
echo "$SERVICE is running"
else
echo "$SERVICE is not running"
fi
}
check_service nginx
check_service ssh
Why functions are useful:
Use read for interactive scripts.
#!/bin/bash
read -p "Enter service name: " SERVICE
systemctl status "$SERVICE"
Confirmation example:
#!/bin/bash
read -p "Are you sure you want to restart nginx? yes/no: " ANSWER
if [ "$ANSWER" = "yes" ]; then
systemctl restart nginx
else
echo "Cancelled"
fi
Useful for:
Command substitution stores command output in a variable.
#!/bin/bash
CURRENT_DATE=$(date)
HOST=$(hostname)
UPTIME=$(uptime -p)
echo "Date: $CURRENT_DATE"
echo "Host: $HOST"
echo "Uptime: $UPTIME"
Example:
FAILED_LOGINS=$(grep "Failed password" /var/log/auth.log | wc -l)
echo "Failed SSH logins: $FAILED_LOGINS"
Preferred syntax:
VALUE=$(command)
Older syntax:
VALUE=`command`
The $(command) syntax is preferred because it is easier to read.
Redirection sends output, errors, or input to/from files.
Overwrite a file:
echo "Starting deployment" > deploy.log
Append to a file:
echo "Deployment completed" >> deploy.log
Redirect errors:
ls /wrong/path 2> error.log
Redirect output and errors:
command > output.log 2>&1
Using tee to show output and save it:
kubectl get pods -A | tee pods-output.txt
Append with tee:
kubectl get events -A | tee -a troubleshooting.log
Useful for:
Pipes send the output of one command to another command.
ps aux | grep nginx
Count failed SSH logins:
grep "Failed password" /var/log/auth.log | wc -l
Find most common IPs in a log:
awk '{print $1}' access.log | sort | uniq -c | sort -nr | head
Find errors in application logs:
grep -i "error" app.log | tail -n 50
Useful for:
These options make scripts safer.
#!/bin/bash
set -euo pipefail
Meaning:
set -e Exit if a command fails
set -u Exit if using an undefined variable
set -o pipefail Fail if any command in a pipe fails
Example:
#!/bin/bash
set -euo pipefail
echo "Starting backup"
tar -czf backup.tar.gz /important/data
echo "Backup completed"
Use this carefully because the script exits immediately when something fails.
Recommended for:
A simple logging pattern makes scripts easier to troubleshoot.
#!/bin/bash
LOG_FILE="script.log"
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}
log "Script started"
log "Checking disk usage"
df -h | tee -a "$LOG_FILE"
log "Script completed"
Why this is useful:
#!/bin/bash
SERVICE=$1
if [ $# -ne 1 ]; then
echo "Usage: $0 <service-name>"
exit 1
fi
if systemctl is-active --quiet "$SERVICE"; then
echo "$SERVICE is running"
else
echo "$SERVICE is not running"
exit 1
fi
Run:
./check-service.sh nginx
#!/bin/bash
THRESHOLD=80
USAGE=$(df / | awk 'NR==2 {print $5}' | sed 's/%//')
if [ "$USAGE" -ge "$THRESHOLD" ]; then
echo "WARNING: Disk usage is ${USAGE}%"
else
echo "Disk usage is normal: ${USAGE}%"
fi
#!/bin/bash
URL=$1
if [ $# -ne 1 ]; then
echo "Usage: $0 <url>"
exit 1
fi
if curl -s --fail "$URL" > /dev/null; then
echo "Site is reachable: $URL"
else
echo "Site is down or unreachable: $URL"
exit 1
fi
Run:
./check-url.sh https://example.com
#!/bin/bash
OUTPUT="system-report-$(date +%F-%H%M%S).log"
{
echo "===== HOSTNAME ====="
hostname
echo "===== UPTIME ====="
uptime
echo "===== DISK USAGE ====="
df -h
echo "===== MEMORY ====="
free -h
echo "===== TOP CPU PROCESSES ====="
ps aux --sort=-%cpu | head
echo "===== TOP MEMORY PROCESSES ====="
ps aux --sort=-%mem | head
echo "===== LISTENING PORTS ====="
ss -tulpn
} | tee "$OUTPUT"
echo "Report saved to $OUTPUT"
#!/bin/bash
LOG_FILE=$1
if [ $# -ne 1 ]; then
echo "Usage: $0 <log-file>"
exit 1
fi
if [ ! -f "$LOG_FILE" ]; then
echo "File not found: $LOG_FILE"
exit 1
fi
echo "Last 50 errors from $LOG_FILE"
grep -i "error" "$LOG_FILE" | tail -n 50
#!/bin/bash
NAMESPACE=${1:-default}
echo "Checking pods in namespace: $NAMESPACE"
kubectl get pods -n "$NAMESPACE" -o wide
echo "Recent events:"
kubectl get events -n "$NAMESPACE" --sort-by=.metadata.creationTimestamp | tail -n 20
Run:
./k8s-check.sh default
./k8s-check.sh production
#!/bin/bash
NAMESPACE=$1
POD=$2
if [ $# -ne 2 ]; then
echo "Usage: $0 <namespace> <pod-name>"
exit 1
fi
OUTPUT="${POD}-logs-$(date +%F-%H%M%S).log"
kubectl logs "$POD" -n "$NAMESPACE" | tee "$OUTPUT"
echo "Logs saved to $OUTPUT"
#!/bin/bash
NAMESPACE=$1
DEPLOYMENT=$2
if [ $# -ne 2 ]; then
echo "Usage: $0 <namespace> <deployment-name>"
exit 1
fi
echo "Restarting deployment $DEPLOYMENT in namespace $NAMESPACE"
kubectl rollout restart deployment "$DEPLOYMENT" -n "$NAMESPACE"
kubectl rollout status deployment "$DEPLOYMENT" -n "$NAMESPACE"
#!/bin/bash
SOURCE_DIR=$1
BACKUP_DIR=$2
if [ $# -ne 2 ]; then
echo "Usage: $0 <source-dir> <backup-dir>"
exit 1
fi
TIMESTAMP=$(date +%F-%H%M%S)
BACKUP_FILE="backup-$TIMESTAMP.tar.gz"
tar -czf "$BACKUP_DIR/$BACKUP_FILE" "$SOURCE_DIR"
echo "Backup created: $BACKUP_DIR/$BACKUP_FILE"
#!/bin/bash
APP_URL=$1
EXPECTED_STATUS=200
if [ $# -ne 1 ]; then
echo "Usage: $0 <app-url>"
exit 1
fi
STATUS_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$APP_URL")
if [ "$STATUS_CODE" -eq "$EXPECTED_STATUS" ]; then
echo "Deployment validation passed. HTTP $STATUS_CODE"
else
echo "Deployment validation failed. HTTP $STATUS_CODE"
exit 1
fi
rm, dd, and mkfs.set -euo pipefail when appropriate.