Overview
Everyone’s Downloads folder becomes a mess. This project automates the clean-up: a script that moves each file into a folder based on its type. Along the way you will practise the habits that separate safe scripts from dangerous ones — a dry-run mode, quoting, never overwriting, and skipping anything unexpected.
Prerequisites
- Managing files with mv and mkdir — lesson 4.
- Scripts, loops, functions and quoting — lesson 12.
Step 1: A Messy Folder
Create a practice folder rather than using your real Downloads at first: mkdir -p ~/Downloads/old-project, then create a mix of files with touch — including an upper-case extension, a name with a space and a file with no extension:
ls DownloadsClick Run to see what this code prints.
Notice that ls puts quotes around my report.docx to show the space is part of the name — exactly the kind of file that breaks careless scripts.
Step 2: The Script
#!/bin/bash# organize.sh: sort files into folders by type# Usage: organize.sh <folder> [--dry-run]set -euo pipefail
dir="${1:?Usage: $0 <folder> [--dry-run]}"dry_run=false[ "${2:-}" = "--dry-run" ] && dry_run=true
folder_for() { case "${1,,}" in jpg|jpeg|png|gif|webp|svg) echo "Images" ;; pdf|doc|docx|txt|md|odt) echo "Documents" ;; mp4|mov|mkv|avi) echo "Videos" ;; mp3|wav|flac) echo "Music" ;; zip|tar|gz|rar|7z) echo "Archives" ;; *) echo "Other" ;; esac}
moved=0for file in "$dir"/*; do [ -f "$file" ] || continue # skip folders name="$(basename "$file")" ext="${name##*.}" [ "$ext" = "$name" ] && ext="" # no extension target="$dir/$(folder_for "$ext")" if $dry_run; then echo "would move $name -> $(basename "$target")/" else mkdir -p "$target" mv -n -- "$file" "$target/" echo "moved $name -> $(basename "$target")/" fi moved=$((moved + 1))doneecho "$moved file(s) $($dry_run && echo "would be" || echo "were") organised."casematches the extension against patterns;${1,,}lower-cases it, so JPG and jpg are treated the same.${name##*.}takes everything after the last dot; if that is the whole name, the file has no extension.[ -f "$file" ] || continueskips folders such as old-project.mv -nnever overwrites an existing file, and--stops a name starting with a dash being read as an option.- Every variable is quoted, so names with spaces work.
Step 3: Dry Run First
Before moving anything, see what the script would do:
chmod +x ~/bin/organize.sh~/bin/organize.sh ~/Downloads --dry-runClick Run to see what this code prints.
Step 4: Organise for Real
~/bin/organize.sh ~/DownloadsClick Run to see what this code prints.
tree ~/DownloadsClick Run to see what this code prints.
The folder old-project was left alone. Running the script again changes nothing, because there are no loose files left — a property called idempotence that every good automation script should have:
~/bin/organize.sh ~/DownloadsClick Run to see what this code prints.
Extend This Project
- Add more categories, such as Code for .py, .js and .html files.
- Sort Images further into year folders using the file’s modification date (date -r file +%Y).
- Run it automatically every evening with cron.
- Write a log of every move, so you can find where a file went.
Summary
You built a useful everyday tool and practised the safety habits that matter in every script that moves or deletes files: dry runs, quoting, no overwriting and idempotence.