Project 02 — File Organizer¶
Build a script that automatically sorts a messy directory by file extension — with logging, dry-run mode, and undo support.
What You Will Build¶
A file organizer that:
- Scans a directory for files and sorts them into subdirectories by type (images, music, documents, archives, etc.)
- Logs every move with timestamp and original path
- Supports --dry-run to preview changes without making them
- Handles files with spaces, dots in names, and no extension
- Can be run repeatedly without duplicating files
Project Files¶
| File | Purpose |
|---|---|
organize.sh |
Main script |
organize.conf |
Extension-to-folder mapping |
organize.log |
Move history |
undo.sh |
Reverses moves using the log file |
README.md |
Usage documentation |
Skills Covered¶
Associative arrays for extension mapping, for loops over files, parameter expansion for extensions, logging, dry-run pattern.
Getting Started¶
Step 1 — Extension Detection¶
for file in "$SOURCE_DIR"/*; do
[[ -f "$file" ]] || continue
ext="${file##*.}"
ext="${ext,,}" # lowercase
echo "$file -> category: $ext"
done
Step 2 — Extension-to-Folder Mapping¶
declare -A FOLDERS
FOLDERS=(
[jpg]="images" [jpeg]="images" [png]="images" [gif]="images" [webp]="images"
[mp3]="music" [wav]="music" [flac]="music" [aac]="music"
[pdf]="documents" [docx]="documents" [xlsx]="documents" [txt]="documents"
[zip]="archives" [tar]="archives" [gz]="archives" [7z]="archives"
[mp4]="videos" [mkv]="videos" [avi]="videos"
[sh]="scripts" [py]="scripts" [rb]="scripts"
)
Step 3 — The Full Organizer¶
#!/usr/bin/env bash
set -euo pipefail
SOURCE="${1:-.}"
DRY_RUN=false
[[ "${2:-}" == "--dry-run" ]] && DRY_RUN=true
LOGFILE="$SOURCE/organize.log"
moved=0
declare -A FOLDERS
# ... (mapping from Step 2) ...
for file in "$SOURCE"/*; do
[[ -f "$file" ]] || continue
filename="${file##*/}"
ext="${filename##*.}"
[[ "$filename" == "$ext" ]] && ext="no-extension"
ext="${ext,,}"
dest_dir="${FOLDERS[$ext]:-other}"
if "$DRY_RUN"; then
echo "[DRY RUN] $filename -> $dest_dir/"
else
mkdir -p "$SOURCE/$dest_dir"
mv -- "$file" "$SOURCE/$dest_dir/$filename"
echo "$(date '+%Y-%m-%d %H:%M:%S') MOVED $filename -> $dest_dir/" >> "$LOGFILE"
(( moved++ ))
fi
done
"$DRY_RUN" || echo "Moved $moved file(s). Log: $LOGFILE"
Step 4 — Undo Script¶
Read the log in reverse and move files back:
#!/usr/bin/env bash
set -euo pipefail
LOGFILE="${1:?Usage: $0 <logfile>}"
[[ -f "$LOGFILE" ]] || { echo "Log file not found"; exit 1; }
tac "$LOGFILE" | while IFS= read -r line; do
filename=$(echo "$line" | awk '{print $3}')
dest_dir=$(echo "$line" | awk '{print $5}' | tr -d '/')
# Reverse the move
echo "Restoring: $filename from $dest_dir/"
done
Stretch Goals¶
- Add
--watchmode usinginotifywaitto organize files as they appear - Add duplicate detection: if a file with the same name exists in the destination, append a counter
- Generate an HTML summary report after organizing
[[01-system-health-monitor]] | [[03-log-analyzer]]