Skip to content

Instantly share code, notes, and snippets.

@SmugZombie
Created September 12, 2025 22:40
Show Gist options
  • Select an option

  • Save SmugZombie/155867d382a760cf0b8f7139a7bc6e57 to your computer and use it in GitHub Desktop.

Select an option

Save SmugZombie/155867d382a760cf0b8f7139a7bc6e57 to your computer and use it in GitHub Desktop.
#!/usr/bin/env bash
# delete-zone-identifier-ads.sh
# Recursively remove Windows Zone.Identifier tags:
# - Plain files named "Zone.Identifier"
# - ADS files like "file:Zone.Identifier"
# - ADS stored as xattrs (ntfs-3g streams_interface=xattr)
set -euo pipefail
usage() {
cat <<EOF
Usage: $0 [-n] <path>
-n dry run (show what would be removed, make no changes)
Notes:
- For ADS-as-xattr removal, requires: getfattr/setfattr (package: 'attr').
- You may need sudo if files are not writable by the current user.
EOF
}
DRY_RUN=0
while getopts ":n" opt; do
case "$opt" in
n) DRY_RUN=1 ;;
*) usage; exit 1 ;;
esac
done
shift $((OPTIND - 1))
if [[ $# -lt 1 ]]; then usage; exit 1; fi
ROOT="$1"
if [[ ! -d "$ROOT" ]]; then
echo "Error: $ROOT is not a directory" >&2
exit 1
fi
run() {
if [[ $DRY_RUN -eq 1 ]]; then
echo "DRY-RUN: $*"
else
eval "$@"
fi
}
echo "Scanning under: $ROOT"
echo "Dry run: $([[ $DRY_RUN -eq 1 ]] && echo YES || echo NO)"
# 1) Plain files named exactly "Zone.Identifier"
echo "Removing plain 'Zone.Identifier' files (if any)..."
if [[ $DRY_RUN -eq 1 ]]; then
find "$ROOT" -type f -name "Zone.Identifier" -print
else
find "$ROOT" -type f -name "Zone.Identifier" -print -delete
fi
# 2) Visible ADS files like 'filename:Zone.Identifier' (ntfs-3g streams_interface=windows)
# Some shells/globs can get confused by ':', so use -name pattern with find.
echo "Removing visible ADS files '*:Zone.Identifier*' (if any)..."
if [[ $DRY_RUN -eq 1 ]]; then
find "$ROOT" -type f -name "*:Zone.Identifier*" -print
else
find "$ROOT" -type f -name "*:Zone.Identifier*" -print -exec rm -f -- {} +
fi
# 3) ADS-as-xattr via ntfs-3g streams_interface=xattr
# Attribute key commonly used by ntfs-3g:
XATTR_KEY='user.DosStream.Zone.Identifier:$DATA'
# Check if getfattr/setfattr are available
HAVE_GETFATTR=0
HAVE_SETFATTR=0
command -v getfattr >/dev/null 2>&1 && HAVE_GETFATTR=1
command -v setfattr >/dev/null 2>&1 && HAVE_SETFATTR=1
if [[ $HAVE_GETFATTR -eq 1 && $HAVE_SETFATTR -eq 1 ]]; then
echo "Scanning for ADS stored as xattr ($XATTR_KEY)..."
# Iterate files once; use -print0 for safety
while IFS= read -r -d '' f; do
if getfattr --absolute-names -n "$XATTR_KEY" --only-values "$f" >/dev/null 2>&1; then
echo "Removing xattr from: $f"
if [[ $DRY_RUN -eq 0 ]]; then
# Best-effort: remove only the Zone.Identifier stream xattr
setfattr -x "$XATTR_KEY" "$f" || {
echo "WARN: Failed to remove xattr on $f" >&2
}
fi
fi
done < <(find "$ROOT" -type f -print0)
else
echo "NOTE: 'getfattr'/'setfattr' not found. Skipping xattr-based ADS removal."
echo " Install the 'attr' package to remove ADS stored as extended attributes."
fi
echo "Done."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment