DEV Community

楊東霖
楊東霖

Posted on • Originally published at devplaybook.cc

Bash Scripting: 20 Scripts Every Developer Needs

Every minute you spend doing a repetitive task manually is a minute stolen from building things. Bash scripting is the fastest way to get those minutes back. You do not need to be a shell wizard — you just need working scripts for the jobs that keep coming up.

Here are 20 practical Bash scripts built for real developer workflows. Each one is production-tested, annotated, and ready to drop into your toolbox.


Bash Script Template (Start Every Script Right)

Before the scripts, here is the template every good Bash script starts with:

#!/usr/bin/env bash
set -euo pipefail

# -e: exit on error
# -u: treat unset variables as errors
# -o pipefail: catch errors in pipes

# Usage: ./script.sh [args]
# Description: What this script does

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
Enter fullscreen mode Exit fullscreen mode

Always use set -euo pipefail. It prevents silent failures that waste hours of debugging.


1. Automated Project Setup

Creates a new project directory with standard structure and git initialization.

#!/usr/bin/env bash
set -euo pipefail

PROJECT_NAME="${1:?Usage: $0 <project-name>}"
BASE_DIR="${2:-$HOME/projects}"
PROJECT_DIR="$BASE_DIR/$PROJECT_NAME"

echo "Creating project: $PROJECT_NAME"
mkdir -p "$PROJECT_DIR"/{src,tests,docs,.github/workflows}
touch "$PROJECT_DIR"/{README.md,.gitignore,.env.example}

cat > "$PROJECT_DIR/README.md" << EOF
# $PROJECT_NAME

## Setup
\`\`\`
cp .env.example .env
\`\`\`
EOF

cd "$PROJECT_DIR"
git init
git add .
git commit -m "chore: initial project scaffold"

echo "Done! Project created at $PROJECT_DIR"
Enter fullscreen mode Exit fullscreen mode

2. Git Branch Cleanup

Deletes merged local branches (and optionally remote ones).

#!/usr/bin/env bash
set -euo pipefail

DEFAULT_BRANCH="${1:-main}"

echo "Fetching remote state..."
git fetch --prune

echo "Merged branches that will be deleted:"
git branch --merged "$DEFAULT_BRANCH" | grep -v "^\*\|$DEFAULT_BRANCH\|main\|master\|develop"

read -rp "Delete these branches? [y/N] " confirm
if [[ "${confirm,,}" == "y" ]]; then
  git branch --merged "$DEFAULT_BRANCH" \
    | grep -v "^\*\|$DEFAULT_BRANCH\|main\|master\|develop" \
    | xargs -r git branch -d
  echo "Done."
fi
Enter fullscreen mode Exit fullscreen mode

3. File Backup with Timestamps

Backs up a file or directory with a timestamp suffix.

#!/usr/bin/env bash
set -euo pipefail

TARGET="${1:?Usage: $0 <file-or-dir>}"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP="${TARGET}.backup_${TIMESTAMP}"

if [[ -d "$TARGET" ]]; then
  cp -r "$TARGET" "$BACKUP"
else
  cp "$TARGET" "$BACKUP"
fi

echo "Backed up to: $BACKUP"
Enter fullscreen mode Exit fullscreen mode

4. Bulk File Rename

Renames files matching a pattern — useful for reformatting filenames in bulk.

#!/usr/bin/env bash
set -euo pipefail

# Usage: ./rename.sh "*.jpeg" "jpg"
# Renames all .jpeg files to .jpg

PATTERN="${1:?Usage: $0 <pattern> <new-extension>}"
NEW_EXT="${2:?}"

for file in $PATTERN; do
  [[ -f "$file" ]] || continue
  base="${file%.*}"
  mv -v "$file" "${base}.${NEW_EXT}"
done
Enter fullscreen mode Exit fullscreen mode

5. Environment Variables Validator

Checks that all required environment variables are set before running an app.

#!/usr/bin/env bash
set -euo pipefail

REQUIRED_VARS=(
  "DATABASE_URL"
  "SECRET_KEY"
  "REDIS_URL"
  "PORT"
)

MISSING=()
for var in "${REQUIRED_VARS[@]}"; do
  if [[ -z "${!var:-}" ]]; then
    MISSING+=("$var")
  fi
done

if [[ ${#MISSING[@]} -gt 0 ]]; then
  echo "ERROR: Missing required environment variables:"
  printf '  - %s\n' "${MISSING[@]}"
  exit 1
fi

echo "All required environment variables are set."
Enter fullscreen mode Exit fullscreen mode

6. Log File Monitor with Alerting

Watches a log file and sends a message when an error pattern appears.

#!/usr/bin/env bash
set -euo pipefail

LOG_FILE="${1:?Usage: $0 <log-file>}"
PATTERN="${2:-ERROR|FATAL|CRITICAL}"
WEBHOOK_URL="${SLACK_WEBHOOK_URL:-}"

tail -F "$LOG_FILE" | while read -r line; do
  if echo "$line" | grep -qE "$PATTERN"; then
    echo "ALERT: $line"
    if [[ -n "$WEBHOOK_URL" ]]; then
      curl -s -X POST "$WEBHOOK_URL" \
        -H 'Content-type: application/json' \
        -d "{\"text\": \"Log alert: \`$line\`\"}"
    fi
  fi
done
Enter fullscreen mode Exit fullscreen mode

7. Simple Deployment Script

Deploys a Node.js app via SSH with zero-downtime swap.

#!/usr/bin/env bash
set -euo pipefail

SERVER="${DEPLOY_SERVER:?Set DEPLOY_SERVER}"
APP_DIR="${DEPLOY_DIR:-/app}"
BRANCH="${1:-main}"

echo "Deploying branch: $BRANCH to $SERVER"

ssh "$SERVER" bash << REMOTE
  set -euo pipefail
  cd $APP_DIR
  git fetch origin
  git checkout $BRANCH
  git pull origin $BRANCH
  npm ci --only=production
  npm run build 2>/dev/null || true
  pm2 reload ecosystem.config.js --update-env
  echo "Deploy complete on \$(hostname) at \$(date)"
REMOTE

echo "Deployment finished."
Enter fullscreen mode Exit fullscreen mode

8. Database Backup (PostgreSQL)

Backs up a Postgres database and compresses with gzip.

#!/usr/bin/env bash
set -euo pipefail

DB_NAME="${1:?Usage: $0 <db-name>}"
BACKUP_DIR="${BACKUP_DIR:-$HOME/db-backups}"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/${DB_NAME}_${TIMESTAMP}.sql.gz"

mkdir -p "$BACKUP_DIR"

echo "Backing up $DB_NAME..."
pg_dump "$DB_NAME" | gzip > "$BACKUP_FILE"
echo "Saved: $BACKUP_FILE ($(du -sh "$BACKUP_FILE" | cut -f1))"

# Keep only last 7 backups
ls -t "$BACKUP_DIR/${DB_NAME}"_*.sql.gz | tail -n +8 | xargs -r rm -v
Enter fullscreen mode Exit fullscreen mode

9. Find and Replace Across Files

Replaces a string across all matching files in a directory.

#!/usr/bin/env bash
set -euo pipefail

SEARCH="${1:?Usage: $0 <search> <replace> [file-pattern]}"
REPLACE="${2:?}"
PATTERN="${3:-*.js}"

echo "Replacing '$SEARCH' with '$REPLACE' in $PATTERN files..."

# macOS uses BSD sed, Linux uses GNU sed
if [[ "$OSTYPE" == "darwin"* ]]; then
  find . -name "$PATTERN" -exec sed -i '' "s|$SEARCH|$REPLACE|g" {} +
else
  find . -name "$PATTERN" -exec sed -i "s|$SEARCH|$REPLACE|g" {} +
fi

echo "Done. Verify with: grep -r '$REPLACE' --include='$PATTERN'"
Enter fullscreen mode Exit fullscreen mode

10. Port Scanner / Port in Use Checker

Checks if a port is in use and shows which process is using it.

#!/usr/bin/env bash
set -euo pipefail

PORT="${1:?Usage: $0 <port>}"

if lsof -Pi ":$PORT" -sTCP:LISTEN -t >/dev/null 2>&1; then
  echo "Port $PORT is IN USE:"
  lsof -Pi ":$PORT" -sTCP:LISTEN
else
  echo "Port $PORT is available."
fi
Enter fullscreen mode Exit fullscreen mode

11. Health Check Script

Polls a URL until it returns 200 — useful after deployments.

#!/usr/bin/env bash
set -euo pipefail

URL="${1:?Usage: $0 <url> [timeout-seconds]}"
TIMEOUT="${2:-60}"
INTERVAL=3
ELAPSED=0

echo "Waiting for $URL to be healthy..."

until curl -sf "$URL" > /dev/null; do
  if [[ $ELAPSED -ge $TIMEOUT ]]; then
    echo "ERROR: $URL did not become healthy within ${TIMEOUT}s"
    exit 1
  fi
  sleep $INTERVAL
  ELAPSED=$((ELAPSED + INTERVAL))
  echo "  Still waiting... (${ELAPSED}s)"
done

echo "Healthy after ${ELAPSED}s"
Enter fullscreen mode Exit fullscreen mode

12. Generate a .env File from .env.example

Copies .env.example and prompts for values for required fields.

#!/usr/bin/env bash
set -euo pipefail

EXAMPLE=".env.example"
OUTPUT=".env"

[[ -f "$EXAMPLE" ]] || { echo "No .env.example found"; exit 1; }

if [[ -f "$OUTPUT" ]]; then
  read -rp ".env already exists. Overwrite? [y/N] " yn
  [[ "${yn,,}" == "y" ]] || exit 0
fi

cp "$EXAMPLE" "$OUTPUT"

# Prompt for empty values
while IFS= read -r line; do
  if [[ "$line" =~ ^([A-Z_]+)=$ ]]; then
    key="${BASH_REMATCH[1]}"
    read -rp "Enter value for $key: " val
    if [[ "$OSTYPE" == "darwin"* ]]; then
      sed -i '' "s|^${key}=$|${key}=${val}|" "$OUTPUT"
    else
      sed -i "s|^${key}=$|${key}=${val}|" "$OUTPUT"
    fi
  fi
done < "$EXAMPLE"

echo "Created $OUTPUT"
Enter fullscreen mode Exit fullscreen mode

13. Monitor Disk Usage and Alert

Warns when a filesystem exceeds a usage threshold.

#!/usr/bin/env bash
set -euo pipefail

THRESHOLD="${1:-85}"

df -h | awk -v t="$THRESHOLD" 'NR>1 {
  gsub(/%/, "", $5)
  if ($5 > t) {
    print "ALERT: " $6 " is at " $5 "% (" $3 " used of " $2 ")"
  }
}'
Enter fullscreen mode Exit fullscreen mode

14. Diff Two API Responses

Compares the JSON output of two endpoints for debugging.

#!/usr/bin/env bash
set -euo pipefail

URL1="${1:?Usage: $0 <url1> <url2>}"
URL2="${2:?}"

RESP1=$(curl -sf "$URL1" | python3 -m json.tool | sort)
RESP2=$(curl -sf "$URL2" | python3 -m json.tool | sort)

diff <(echo "$RESP1") <(echo "$RESP2") && echo "Responses are identical." || true
Enter fullscreen mode Exit fullscreen mode

15. Auto-Commit Script

Stages, commits, and pushes all changes with a timestamped message — useful for automated content or data repos.

#!/usr/bin/env bash
set -euo pipefail

REPO_DIR="${1:-.}"
BRANCH="${2:-main}"
MSG="${3:-"chore: auto-update $(date '+%Y-%m-%d %H:%M')"}"

cd "$REPO_DIR"

if [[ -z "$(git status --porcelain)" ]]; then
  echo "No changes to commit."
  exit 0
fi

git add .
git commit -m "$MSG"
git push origin "$BRANCH"
echo "Pushed: $MSG"
Enter fullscreen mode Exit fullscreen mode

16. Extract All URLs from a File

Pulls every URL from a file or HTML page.

#!/usr/bin/env bash
set -euo pipefail

INPUT="${1:?Usage: $0 <file-or-url>}"

if [[ "$INPUT" =~ ^https?:// ]]; then
  curl -sf "$INPUT"
else
  cat "$INPUT"
fi | grep -oP 'https?://[^\s"<>]+' | sort -u
Enter fullscreen mode Exit fullscreen mode

17. Recursive File Size Report

Shows the largest files and directories in a path.

#!/usr/bin/env bash
set -euo pipefail

DIR="${1:-.}"
COUNT="${2:-20}"

echo "=== Top $COUNT largest files in $DIR ==="
find "$DIR" -type f -printf '%s\t%p\n' 2>/dev/null \
  | sort -rn | head -"$COUNT" \
  | awk '{printf "%-10s %s\n", $1/1024/1024" MB", $2}'

echo ""
echo "=== Directory sizes ==="
du -sh "$DIR"/*/  2>/dev/null | sort -rh | head -"$COUNT"
Enter fullscreen mode Exit fullscreen mode

18. Wait for a Docker Container to Be Ready

Polls a container's health status before proceeding.

#!/usr/bin/env bash
set -euo pipefail

CONTAINER="${1:?Usage: $0 <container-name> [timeout]}"
TIMEOUT="${2:-30}"
ELAPSED=0

echo "Waiting for $CONTAINER to be healthy..."

until [[ "$(docker inspect -f '{{.State.Health.Status}}' "$CONTAINER" 2>/dev/null)" == "healthy" ]]; do
  if [[ $ELAPSED -ge $TIMEOUT ]]; then
    echo "ERROR: $CONTAINER not healthy after ${TIMEOUT}s"
    docker logs --tail=20 "$CONTAINER"
    exit 1
  fi
  sleep 2
  ELAPSED=$((ELAPSED + 2))
done

echo "$CONTAINER is healthy after ${ELAPSED}s"
Enter fullscreen mode Exit fullscreen mode

19. Run Script with a Timeout

Kills a script if it runs longer than expected.

#!/usr/bin/env bash
set -euo pipefail

TIMEOUT="${1:?Usage: $0 <timeout-seconds> <command...>}"
shift

timeout "$TIMEOUT" "$@"
EXIT=$?

if [[ $EXIT -eq 124 ]]; then
  echo "ERROR: Command timed out after ${TIMEOUT}s"
  exit 1
fi

exit $EXIT
Enter fullscreen mode Exit fullscreen mode

20. Daily Summary Script

Shows a snapshot of your day: git commits, system info, running processes.

#!/usr/bin/env bash
set -euo pipefail

echo "=== Daily Dev Summary: $(date '+%A, %B %d %Y') ==="
echo ""

echo "--- Git Activity (today) ---"
git log --all --since=midnight --oneline --author="$(git config user.email)" 2>/dev/null \
  || echo "(not a git repo)"
echo ""

echo "--- System ---"
echo "Uptime: $(uptime -p 2>/dev/null || uptime)"
echo "CPU:    $(top -bn1 | grep 'Cpu(s)' | awk '{print $2}')% used"
echo "Memory: $(free -h 2>/dev/null | awk '/Mem:/{print $3"/"$2}' || vm_stat | awk 'NR==2{print $3}')"
echo "Disk:   $(df -h / | tail -1 | awk '{print $3"/"$2" ("$5" used)"}')"
echo ""

echo "--- Listening Ports ---"
ss -tlnp 2>/dev/null | grep LISTEN | awk '{print $4}' | sort -u \
  || netstat -tlnp 2>/dev/null | grep LISTEN | awk '{print $4}' | sort -u
Enter fullscreen mode Exit fullscreen mode

Bash Scripting Quick Reference

Pattern Purpose
${VAR:?msg} Require variable, exit with message if unset
${VAR:-default} Use default if variable is unset
$(command) Command substitution
$? Exit code of last command
>&2 Redirect to stderr
2>/dev/null Suppress error output
`
{% raw %}[[ -f file ]] Test if file exists
[[ -d dir ]] Test if directory exists
[[ -z "$VAR" ]] Test if string is empty
read -rp "Prompt: " var Read user input
xargs -r Only run if input exists

These 20 scripts cover 80% of the repetitive work in a typical developer's day. Copy them, adapt them to your environment, and add them to a personal ~/bin/ directory on your PATH. Your future self will thank you.


Level Up Your Dev Workflow

Found this useful? Explore DevPlaybook — cheat sheets, tool comparisons, and hands-on guides for modern developers.

🛒 Get the DevToolkit Starter Kit on Gumroad — 40+ browser-based dev tools, source code + deployment guide included.

Top comments (0)