Enhancements — File Organizer¶
Extend the core organizer once the basics work.
Enhancement 1 — Undo / Rollback¶
Write a manifest so you can reverse every move:
MANIFEST="${SCRIPT_DIR}/organizer_manifest.csv"
move_file() {
local src="$1" dest_dir="$2"
local filename dest
filename="$(basename "$src")"
dest="$(unique_dest "${dest_dir}/${filename}")"
[[ "$DRY_RUN" == true ]] && { log_info "[DRY RUN] $src → $dest"; return; }
mkdir -p "$dest_dir"
mv -- "$src" "$dest"
echo "$(date +%s),${src},${dest}" >> "$MANIFEST"
log_info "moved: $src → $dest"
}
Add an --undo flag that reads the manifest and reverses every move:
undo_moves() {
[[ -f "$MANIFEST" ]] || { echo "No manifest found at $MANIFEST"; exit 1; }
tac "$MANIFEST" | while IFS=, read -r ts src dest; do
if [[ -f "$dest" ]]; then
mkdir -p "$(dirname "$src")"
mv -- "$dest" "$src"
echo "restored: $dest → $src"
else
echo "WARN: $dest not found, skipping"
fi
done
echo "Undo complete"
}
Enhancement 2 — Custom Rules via Config¶
Let users define their own category mappings in a config file:
# ~/.config/file_organizer.conf
# FORMAT: category:ext1,ext2,ext3
Work:doc,docx,xlsx,pptx,pdf
Personal:jpg,png,mp4,mp3
Projects:sh,py,go,rs,js
Load custom rules:
load_custom_rules() {
local conf="${HOME}/.config/file_organizer.conf"
[[ -f "$conf" ]] || return 0
while IFS=: read -r category exts; do
[[ "$category" =~ ^# ]] && continue # skip comments
IFS=, read -r -a ext_list <<< "$exts"
for ext in "${ext_list[@]}"; do
CUSTOM_RULES["${ext,,}"]="$category"
done
done < "$conf"
}
declare -A CUSTOM_RULES
get_category() {
local ext="${1,,}"
# Check custom rules first
[[ -n "${CUSTOM_RULES[$ext]:-}" ]] && echo "${CUSTOM_RULES[$ext]}" && return
# Fall back to built-in mapping
case "$ext" in
jpg|jpeg|png|gif|bmp|svg|webp) echo "Images" ;;
# ... rest of built-in rules
*) echo "Other" ;;
esac
}
Enhancement 3 — Recursive Mode¶
Organize nested directories, not just the top level:
# Add flag: -r, --recursive
RECURSIVE=false
# Change find command in main():
if [[ "$RECURSIVE" == true ]]; then
find_opts=()
else
find_opts=(-maxdepth 1)
fi
while IFS= read -r -d '' file; do
...
done < <(find "$target" "${find_opts[@]}" -type f -print0)
Recursive mode moves files out of nested directories
If you have Downloads/Work/report.pdf, recursive mode will move it to Downloads/Documents/report.pdf. The Work/ subdirectory won't be deleted automatically. Add empty-directory cleanup if you want it fully tidy.
Enhancement 4 — Watch Mode (inotify)¶
Continuously organize a directory as new files arrive:
# Requires: inotifywait (sudo apt install inotify-tools)
watch_dir() {
local target="$1"
echo "Watching $target for new files..."
inotifywait -m -e close_write,moved_to "$target" --format '%f' |
while read -r filename; do
local filepath="${target}/${filename}"
[[ -f "$filepath" ]] || continue
local ext="${filename##*.}"
local category
category="$(get_category "$ext")"
move_file "$filepath" "${target}/${category}"
log_info "watch: auto-moved $filename → $category/"
done
}
Enhancement 5 — Date-Based Sorting¶
For image-heavy workflows, sort by creation date instead of extension:
get_date_category() {
local file="$1"
# Try EXIF date first (requires exiftool)
local date_str
date_str=$(exiftool -DateTimeOriginal -d "%Y/%m" "$file" 2>/dev/null | awk -F': ' '{print $2}' | tr -d ' ')
if [[ -n "$date_str" ]]; then
echo "$date_str"
return
fi
# Fall back to file modification time
date -d "@$(stat -c %Y "$file")" '+%Y/%m'
}
This creates a hierarchy like Images/2024/01/photo.jpg instead of Images/photo.jpg.
Enhancement 6 — Statistics Report¶
After organizing, print a summary table:
print_stats() {
echo ""
echo "=== Organizer Summary ==="
printf "%-15s %s\n" "Category" "Files moved"
printf "%-15s %s\n" "--------" "-----------"
for dir in "${TARGET_DIR}"/*/; do
local category count
category="$(basename "$dir")"
count=$(find "$dir" -maxdepth 1 -type f | wc -l)
(( count > 0 )) && printf "%-15s %d\n" "$category" "$count"
done
}