rename_safe/rename_safe.sh
2025-07-20 19:45:59 +00:00

97 lines
No EOL
2.3 KiB
Bash

#!/bin/bash
# Handle --dry-run flag
DRY_RUN=0
if [ "$1" = "--dry-run" ]; then
DRY_RUN=1
shift
fi
# Directory to process (default: current directory)
DIR="."
if [ -n "$1" ]; then
DIR="$1"
fi
# Function to process and rename a file
process_file() {
local original="$1"
local base ext modified sanitized_base
# Skip directories and non-regular files
[ -f "$original" ] || return 0
# Split base and extension
if [[ "$original" == *.* ]]; then
ext=".${original##*.}"
base="${original%.*}"
else
base="$original"
ext=""
fi
# Sanitize base only, not the extension
sanitized_base=$(echo "$base" | tr ' ' '_' | tr '[:upper:]' '[:lower:]')
sanitized_base=$(echo "$sanitized_base" | sed 's/[^a-zA-Z0-9._-]//g')
# Truncate base to 250 chars to avoid hitting filesystem limits
base_truncated="${sanitized_base:0:250}"
# Rebuild filename with sanitized base and original extension
modified="$base_truncated$ext"
# Collapse multiple underscores
modified=$(echo "$modified" | tr -s '_')
# Trim leading/trailing underscores
modified=$(echo "$modified" | sed 's/^_//' | sed 's/_$//')
# Skip if empty
if [ -z "$modified" ]; then
echo "Error: Empty filename after sanitizing '$original'" >&2
return 1
fi
# Skip if no change
if [ "$original" = "$modified" ]; then
echo "No changes needed: '$original'"
return 0
fi
# Collision handling
counter=1
original_modified="$modified"
while [ -e "$modified" ]; do
modified="${base_truncated}_${counter}${ext}"
# Truncate again to avoid going over 255 chars
modified="${modified:0:255}"
((counter++))
done
# Dry run
if [ "$DRY_RUN" -eq 1 ]; then
echo "Dry-run: Would rename '$original' → '$modified'"
return 0
fi
# Rename file
if mv -- "$original" "$modified"; then
echo "Renamed: '$original' → '$modified'"
else
echo "Error: Failed to rename '$original'" >&2
return 1
fi
}
# Enter target directory
cd "$DIR" || { echo "Error: Failed to change directory to '$DIR'" >&2; exit 1; }
# Process all non-hidden files
for file in *; do
process_file "$file"
done
# Optional: process hidden files (uncomment below)
# for file in .??*; do
# process_file "$file"
# done