Nandishwar Singh

Home / Learn Linux / Project 1

Clean up a messy folder

Sort hundreds of files by type and age, search logs, archive the old ones and link the latest.

Project
1 of 5
Time
About 60 minutes
Uses
Lessons 4 to 12

On this page

  1. What you'll build
  2. Step 0: make the mess
  3. Step 1: survey it
  4. Step 2: make the new layout
  5. Step 3: delete the junk, safely
  6. Step 4: sort by type
  7. Step 5: find the files with spaces
  8. Step 6: what's wrong with the servers?
  9. Step 7: archive the old files
  10. Step 8: point latest at the newest data
  11. Step 9: fix the config
  12. Step 10: the final check
  13. Take it further

What you'll build

Everyone has a folder like this: a downloads directory with hundreds of files, photos mixed with invoices and logs, names with spaces, empty leftovers and files nobody has opened in a year. You'll clean it up using only what you learned in lessons 4 to 12:

Each step says what to do, gives you a hint if you need one, shows what you should see when it worked, and hides a full solution. Try each step on your own first.

Step 0: make the mess

Paste this whole block into your VM. It builds ~/project1/downloads with 113 files. Don't worry about understanding it all; it's just setup:

$ mkdir -p ~/project1/downloads && cd ~/project1/downloads
$ touch IMG_{1001..1040}.jpg holiday-{1..12}.JPG screenshot-{1..15}.png
$ touch invoice-2025-{01..12}.pdf invoice-2026-{01..09}.pdf
$ touch notes-{1..10}.txt "meeting notes.txt" "final final v2.txt"
$ seq 1 500 > sales-2026-08.csv; seq 1 800 > sales-2026-09.csv; seq 1 650 > sales-2026-10.csv
$ printf "INFO start\nERROR db timeout\nINFO ok\nWARN slow query\nERROR db timeout\nERROR disk full\n" | tee server-1.log server-2.log > /dev/null
$ printf "INFO start\nERROR auth failed\nERROR db timeout\nWARN slow query\n" > server-3.log
$ printf "app_name = shop\ndata_dir = /home/olduser/data\nlog_dir = /home/olduser/logs\nport = 8080\n" > app.conf
$ touch empty-{1..6}.tmp
$ touch -d "200 days ago" IMG_10{01..20}.jpg invoice-2025-*.pdf
$ touch -d "120 days ago" notes-{1..5}.txt sales-2026-08.csv
$ touch -d "3 days ago" sales-2026-09.csv
$ ls | wc -l
113

Step 1: survey it

Before changing anything, find out what's there. Answer these three questions with one command each:

  1. How many files are there?
  2. How much space do they take?
  3. How many files of each extension are there, most common first?
Hint

For 3, sed can strip everything up to the last dot (s/.*\.//), then sort | uniq -c | sort -rn counts and ranks, just like the page counts in lesson 8.

Solution
$ find . -type f | wc -l
113
$ du -sh .
32K     .
$ ls | sed 's/.*\.//' | sort | uniq -c | sort -rn
     40 jpg
     21 pdf
     15 png
     12 txt
     12 JPG
      6 tmp
      3 log
      3 csv
      1 conf

Notice JPG and jpg are counted separately. Linux is case-sensitive, so you'll need -iname when you sort the photos.

Step 2: make the new layout

Inside ~/project1, create a sorted directory with images, documents, data, logs, config and other inside it, in one command.

Solution
$ mkdir -p sorted/{images,documents,data,logs,config,other}
$ tree sorted
sorted
├── config
├── data
├── documents
├── images
├── logs
└── other

7 directories, 0 files

Step 3: delete the junk, safely

Some files are empty leftovers. Find every empty file in downloads, look at the list, then delete exactly those.

Hint

find has -empty. Run it without -delete first. Careful: the photos and invoices you made with touch are empty too, so limit the search to the .tmp files.

Solution
$ find . -type f -name "*.tmp" -empty
./empty-1.tmp
./empty-2.tmp
./empty-3.tmp
./empty-4.tmp
./empty-5.tmp
./empty-6.tmp
$ find . -type f -name "*.tmp" -empty -delete
$ ls *.tmp
ls: cannot access '*.tmp': No such file or directory

This step is why you always look before you delete. find . -empty -delete on its own would have wiped every photo and invoice in this practice folder, because touch made them all empty.

Step 4: sort by type

Move the files into place:

Hint

find with \( -iname "*.jpg" -o -iname "*.png" \) matches all the photos. GNU mv has -t dir, which puts the destination first, so -exec mv -t ../sorted/images {} + moves them in one go. Names with spaces are safe this way, because find passes each name as one argument.

Solution
$ find . -type f \( -iname "*.jpg" -o -iname "*.png" \) -exec mv -t ../sorted/images {} +
$ find . -type f \( -name "*.pdf" -o -name "*.txt" \) -exec mv -t ../sorted/documents {} +
$ mv *.csv ../sorted/data/
$ mv *.log ../sorted/logs/
$ mv *.conf ../sorted/config/
$ ls | wc -l
0

downloads is empty now. Check where everything went:

$ for d in sorted/*; do echo "$d $(ls "$d" | wc -l)"; done
sorted/config 1
sorted/data 3
sorted/documents 33
sorted/images 67
sorted/logs 3
sorted/other 0

That for loop counts each directory. Lesson 28 explains loops; for now, copy it as it is.

Step 5: find the files with spaces

Two documents have spaces in their names, which is annoying on a server. Find them, then rename them to use dashes instead.

Hint

find with -name "* *". To rename one, quote the old name: mv "meeting notes.txt" meeting-notes.txt.

Solution
$ find . -name "* *"
./meeting notes.txt
./final final v2.txt
$ mv "meeting notes.txt" meeting-notes.txt
$ mv "final final v2.txt" final-v2.txt
$ find . -name "* *" | wc -l
0

Step 6: what's wrong with the servers?

The three logs in sorted/logs hold the real story. Produce a report:

  1. How many ERROR lines does each log have?
  2. Across all logs, which error messages happen most often? Show each message once, with its count, most frequent first.
  3. Save the answer to 2 in ~/project1/error-report.txt and show it at the same time.
Hint

grep -c counts per file. For 2, grep -h leaves out the file names, sed can strip the ERROR prefix, and sort | uniq -c | sort -rn ranks them. tee saves and shows.

Solution
$ grep -c ERROR *.log
server-1.log:3
server-2.log:3
server-3.log:2
$ grep -h ERROR *.log | sed 's/^ERROR //' | sort | uniq -c | sort -rn | tee ~/project1/error-report.txt
      5 db timeout
      2 disk full
      1 auth failed

db timeout is the problem: five times across three servers.

Step 7: archive the old files

Anything not modified in the last 90 days should be packed into one archive and removed from sorted.

  1. List every file under sorted older than 90 days and save the list to old-files.txt.
  2. Make old-YYYY-MM-DD.tar.gz (today's date) from that list, removing the files as they're added.
  3. Check the archive's contents and how many files are left in sorted.
Hint

find sorted -type f -mtime +90. GNU tar reads names from a file with -T old-files.txt, and --remove-files deletes each file after it's safely in the archive.

Solution
$ find sorted -type f -mtime +90 > old-files.txt
$ wc -l < old-files.txt
38
$ tar -czf old-$(date +%F).tar.gz --remove-files -T old-files.txt
$ tar -tzf old-*.tar.gz | wc -l
38
$ find sorted -type f | wc -l
69

Step 8: point latest at the newest data

Make a symlink ~/project1/latest.csv that always points at the newest file in sorted/data. Then check where it points.

Hint

ls -t lists newest first, so ls -t sorted/data/*.csv | head -n 1 is the newest. Put that inside $(...) in an ln -sfn command.

Solution
$ ls -lt sorted/data
total 8
-rw-rw-r-- 1 ubuntu ubuntu 2492 Oct 21 11:37 sales-2026-10.csv
-rw-rw-r-- 1 ubuntu ubuntu 3092 Oct 18 11:37 sales-2026-09.csv
$ ln -sfn "$(ls -t sorted/data/*.csv | head -n 1)" latest.csv
$ ls -l latest.csv
lrwxrwxrwx 1 ubuntu ubuntu 29 Oct 21 11:37 latest.csv -> sorted/data/sales-2026-10.csv
$ wc -l latest.csv
650 latest.csv

Careful: the newest modified file isn't always the one with the latest date in its name. Here sales-2026-10.csv was created last, so it wins.

Step 9: fix the config

sorted/config/app.conf still points at a user who doesn't exist any more. Change every /home/olduser to /home/ubuntu, and change the port to 9000, keeping a backup. Show the result.

Hint

Paths are full of slashes, so use another separator: s|old|new|g. Two changes, one sed with -e twice.

Solution
$ sed -i.bak -e 's|/home/olduser|/home/ubuntu|g' -e 's/^port = .*/port = 9000/' app.conf
$ cat app.conf
app_name = shop
data_dir = /home/ubuntu/data
log_dir = /home/ubuntu/logs
port = 9000
$ ls
app.conf  app.conf.bak

Step 10: the final check

Show the finished layout, and remove the empty downloads directory without risking anything that's still inside it.

Solution
$ rmdir downloads
$ tree -L 3 --filelimit 15
.
├── error-report.txt
├── latest.csv -> sorted/data/sales-2026-10.csv
├── old-2026-10-21.tar.gz
├── old-files.txt
└── sorted
    ├── config
    │   ├── app.conf
    │   └── app.conf.bak
    ├── data
    │   ├── sales-2026-09.csv
    │   └── sales-2026-10.csv
    ├── documents  [16 entries exceeds filelimit, not opening dir]
    ├── images  [47 entries exceeds filelimit, not opening dir]
    ├── logs
    │   ├── server-1.log
    │   ├── server-2.log
    │   └── server-3.log
    └── other

8 directories, 11 files

rmdir would have refused if anything were left. --filelimit 15 stops tree from listing directories with more than 15 entries and shows how many there are instead, which keeps the big images and documents directories from flooding the screen.

Take it further