Skip to content

Instantly share code, notes, and snippets.

@andyg2
Last active January 3, 2026 17:52
Show Gist options
  • Select an option

  • Save andyg2/a10d89daab45a115e6bdeb58481fc9fb to your computer and use it in GitHub Desktop.

Select an option

Save andyg2/a10d89daab45a115e6bdeb58481fc9fb to your computer and use it in GitHub Desktop.
A Bash script to resize and optimize images in a directory (optionally recursively), supporting PNG, JPEG/JPG, and WebP formats.
#!/bin/bash
# PREFLIGHT
# sudo apt update && sudo apt install -y imagemagick optipng jpegoptim webp tar findutils
set -euo pipefail
# ----------------------------
# Defaults
# ----------------------------
DIR=""
RECURSIVE=false
RESIZE=""
MIN_SIZE=""
BACKUP=false
# ----------------------------
# Requirements
# ----------------------------
need() {
command -v "$1" >/dev/null 2>&1 || {
echo "Missing required tool: $1" >&2
exit 1
}
}
need find
need tar
need optipng
need jpegoptim
need cwebp
need mogrify
# ----------------------------
# Usage
# ----------------------------
usage() {
cat <<EOF
Usage:
$0 -D DIR [options]
Options:
-r Include subdirectories
-d WxH Resize to max width/height before optimizing (e.g. 1920x1080)
-s SIZE Minimum file size to consider (e.g. 300k, 1M)
-b Create backup tar.gz before modifying files
-h Show this help
Examples:
$0 -d images
$0 -d images -r
$0 -d images -r -d 1920x1080 -s 300k -b
EOF
exit 1
}
# ----------------------------
# Parse args
# ----------------------------
while getopts ":D:rd:s:bh" opt; do
case "$opt" in
D) DIR="$OPTARG" ;;
r) RECURSIVE=true ;;
d) RESIZE="$OPTARG" ;;
s) MIN_SIZE="$OPTARG" ;;
b) BACKUP=true ;;
h) usage ;;
*) usage ;;
esac
done
[[ -z "$DIR" ]] && usage
[[ ! -d "$DIR" ]] && { echo "Directory not found: $DIR"; exit 1; }
# ----------------------------
# Find depth
# ----------------------------
DEPTH=(-maxdepth 1)
$RECURSIVE && DEPTH=()
# ----------------------------
# Build file list
# ----------------------------
FIND_CMD=(find "$DIR" "${DEPTH[@]}" -type f \( -iname "*.png" -o -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.webp" \))
[[ -n "$MIN_SIZE" ]] && FIND_CMD+=(-size +"$MIN_SIZE")
mapfile -t FILES < <("${FIND_CMD[@]}")
[[ ${#FILES[@]} -eq 0 ]] && { echo "No images found."; exit 0; }
# ----------------------------
# Backup only affected files
# ----------------------------
if $BACKUP; then
TS=$(date +"%Y%m%d-%H%M%S")
TAR="image-backup-$TS.tar.gz"
echo "Creating backup: $TAR"
tar -czf "$TAR" --absolute-names "${FILES[@]}"
fi
echo "Processing ${#FILES[@]} images…"
[[ -n "$RESIZE" ]] && echo "Resizing to max: $RESIZE"
$RECURSIVE && echo "Including subdirectories"
[[ -n "$MIN_SIZE" ]] && echo "Only processing files larger than: $MIN_SIZE"
# ----------------------------
# Process images
# ----------------------------
for f in "${FILES[@]}"; do
echo "→ $f"
# Resize first (never upscale)
if [[ -n "$RESIZE" ]]; then
mogrify -resize "${RESIZE}>" "$f"
fi
case "${f,,}" in
*.png)
optipng -o7 -strip all "$f" >/dev/null
;;
*.jpg|*.jpeg)
jpegoptim --all-progressive --max=75 -o -p --strip-all "$f" >/dev/null
;;
*.webp)
cwebp -q 75 "$f" -o "$f" >/dev/null
;;
esac
done
echo "Done."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment