Skip to content

Instantly share code, notes, and snippets.

@guessi
Created April 11, 2026 11:53
Show Gist options
  • Select an option

  • Save guessi/5969ebabf138b144dd38985db7bb465a to your computer and use it in GitHub Desktop.

Select an option

Save guessi/5969ebabf138b144dd38985db7bb465a to your computer and use it in GitHub Desktop.
Strip all sensitive metadata from image files.
#!/usr/bin/env bash
#
# strip-metadata.sh — Strip all sensitive metadata from image files.
#
# Images can contain hidden metadata that leaks private information:
# - EXIF: camera model, serial number, software version, timestamps
# - GPS: exact latitude/longitude where the photo was taken
# - ICC profiles: color profiles that can reveal the device or software used
# - XMP/IPTC: author names, copyright info, editing history
#
# This script uses exiftool to remove ALL of the above in one pass.
#
# Usage:
# ./strip-metadata.sh [options] <image> [image2 ...]
#
# Options:
# -n, --dry-run Show metadata without removing
# -y, --yes Skip confirmation prompt
# -h, --help Show this help message
#
# Examples:
# ./strip-metadata.sh photo.jpg Single file
# ./strip-metadata.sh *.jpg *.png Batch process
# ./strip-metadata.sh -n photo.jpg Preview what would be removed
# ./strip-metadata.sh -y *.jpg Strip without confirmation
set -o errexit
set -o nounset
set -o pipefail
usage() {
sed -n '/^# Usage:/,/^[^#]/{/^#/s/^# \{0,1\}//p;}' "$0"
exit "${1:-0}"
}
# Require exiftool — the standard tool for reading/writing image metadata
if ! command -v exiftool &>/dev/null; then
echo "Error: exiftool is required." >&2
echo " macOS: brew install exiftool" >&2
echo " Debian: apt install libimage-exiftool-perl" >&2
echo " Fedora: dnf install perl-Image-ExifTool" >&2
exit 1
fi
echo "Using: $(command -v exiftool)"
echo
DRY_RUN=false
SKIP_CONFIRM=false
while [[ $# -gt 0 ]]; do
case "$1" in
-n|--dry-run) DRY_RUN=true; shift ;;
-y|--yes) SKIP_CONFIRM=true; shift ;;
-h|--help) usage 0 ;;
--) shift; break ;;
-*) echo "Unknown option: $1" >&2; usage 1 ;;
*) break ;;
esac
done
if [[ $# -eq 0 ]]; then
echo "Error: no files specified." >&2
usage 1
fi
# Warn before destructive operation
if ! $DRY_RUN && ! $SKIP_CONFIRM; then
echo "This will permanently strip metadata from $# file(s) in place."
read -rp "Continue? [y/N] " answer
[[ "$answer" =~ ^[Yy]$ ]] || { echo "Aborted."; exit 0; }
fi
processed=0
skipped=0
for file in "$@"; do
if [[ -L "$file" ]]; then
echo "Skipping: $file (symlink)" >&2
skipped=$((skipped + 1))
continue
fi
if [[ ! -f "$file" ]]; then
echo "Skipping: $file (not found)" >&2
skipped=$((skipped + 1))
continue
fi
mime=$(file --brief --mime-type "$file")
if [[ "$mime" != image/* ]]; then
echo "Skipping: $file (not an image: $mime)" >&2
skipped=$((skipped + 1))
continue
fi
echo "=== $file ==="
if $DRY_RUN; then
# -G1: show metadata group names (e.g. [EXIF], [GPS], [ICC_Profile])
# so you can see exactly where each tag comes from
exiftool -G1 "$file"
echo
processed=$((processed + 1))
continue
fi
# -all= Remove all metadata tags (EXIF, XMP, IPTC, GPS, etc.)
# -icc_profile:all= Also remove ICC color profiles (not covered by -all=)
# -overwrite_original Modify the file in place without creating a backup
if ! exiftool -all= -icc_profile:all= -overwrite_original "$file"; then
echo "Warning: could not strip metadata from $file" >&2
skipped=$((skipped + 1))
echo
continue
fi
# Verify: count remaining tags, excluding ExifTool/System/File groups
# which are structural (image dimensions, encoding) and can't be removed
remaining=$(exiftool -G1 -s "$file" | grep -cv '^\[ExifTool\]\|^\[System\]\|^\[File\]' || true)
echo "Done. Remaining non-structural tags: $remaining"
echo
processed=$((processed + 1))
done
echo "---"
if $DRY_RUN; then
echo "Previewed: $processed | Skipped: $skipped"
else
echo "Stripped: $processed | Skipped: $skipped"
fi
if [[ $processed -eq 0 ]]; then
exit 1
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment