Created
September 12, 2025 22:40
-
-
Save SmugZombie/155867d382a760cf0b8f7139a7bc6e57 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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