!/usr/bin/env bash
#!/usr/bin/env bash set -euo pipefail
Usage: ./run.sh [TARGET_DIR]
TARGET_DIR=”${1:-.}”
shopt -s globstar nullglob
Normalise filename: lowercase, replace spaces with underscores, remove special chars except . _ -
normalize_name() { local name=”$1” local base ext newbase
if [[ “$name” == . ]]; then base=”${name%.}” ext=”.${name##.}” else base=”$name” ext=”” fi
# lowercase newbase=”$(printf ‘%s’ “$base” | tr ‘[:upper:]’ ‘[:lower:]’)” # replace spaces with underscores newbase=”${newbase// /}” # remove any characters except a-z0-9.- (keep underscore and hyphen) newbase=”$(printf ‘%s’ “$newbase” | sed ‘s/[^a-z0-9._-]//g’)”
printf ‘%s%s’ “$newbase” “$ext” }
Iterate files recursively under TARGET_DIR
while IFS= read -r -d ‘’ file; do [[ -f “$file” ]] || continue
filename=”$(basename “$file”)”
# get modification time in format YYYY-MM (GNU stat) mod_ym=”$(stat -c %y “$file” | cut -d’ ‘ -f1 | awk -F- ‘{printf “%s-%02d”, $1, $2}’)”
target_dir=”$TARGET_DIR/$mod_ym” mkdir -p “$target_dir”
newname=”$(normalize_name “$filename”)”
# ensure unique filename in target directory dest=”$target_dir/$newname” if [[ -e “$dest” ]]; then base=”${newname%.}” ext=”” if [[ “$newname” == *. ]]; then ext=”.${newname##*.}” fi counter=1 while [[ -e “$target_dir/${base}$counter${ext}” ]]; do ((counter++)) done dest=”$target_dir/${base}$counter${ext}” fi
mv -n “$file” “$dest” done < <(find “$TARGET_DIR” -type f -print0)
echo “Done.”