#!/opt/homebrew/bin/bash # # x.sh — sanitize filenames: lowercase, replace non-alnum/dot with underscores, # collapse runs of underscores, trim, and never overwrite a *different* existing file. # shopt -s globstar SAVEIFS=$IFS IFS=$'\n' # only split on newlines sanitize() { local name="$1" # 1) lowercase name="${name,,}" # 2) anything not a–z, 0–9, or dot → underscore name="${name//[^a-z0-9.]/_}" # 3) collapse multiple underscores → one underscore name="$(echo "$name" | sed -E 's/_+/_/g')" # 4) remove underscore adjacent to dots name="$(echo "$name" | sed -E 's/_\.|\._/./g')" # 5) trim leading/trailing underscores name="$(echo "$name" | sed -E 's/^_+|_+$//g')" printf '%s' "$name" } export -f sanitize # depth-first traversal so directories are renamed after their contents find . -depth -print0 | while IFS= read -r -d '' src; do dir=$(dirname "$src") base=$(basename "$src") dst=$(sanitize "$base") if [[ "$base" != "$dst" ]]; then target="$dir/$dst" if [[ -e "$target" ]]; then # If the only difference is case (macOS/APFS), allow it: if [[ "${src,,}" = "${target,,}" ]]; then mv -v "$src" "$target" else echo "Skipping: '$src' → '$target' (target exists)" fi else mv -v "$src" "$target" fi fi done IFS=$SAVEIFS