Module 2: Working with Files and Text

8. Project: audit a project you do not know

Description

At some point in your first job someone is going to hand you a repository you did not write, with no documentation, and just tell you "take a look at what's in there and let me know if you find anything weird." Nobody is going to walk you through the structure line by line. In this lesson you are going to simulate exactly that situation in two parts. First you rebuild a project tree from a map, using as few commands as possible with mkdir -p and touch. Then you actually audit it: you answer five concrete questions with the exact command you used and the result it produced — not a general impression of what you saw. To close, you apply the safe cleanup you learned in lesson 3 to leave your practice directory the way it was.

This is not filler. Auditing before touching anything is what separates someone who breaks something in their first week from someone who does not: when you inherit code, your first responsibility is not to write, it is to understand what is there and leave written evidence of what you found, so the next person — who might be you in six months, with all of this forgotten — does not have to repeat the reconnaissance from scratch.

Connection to the module: this is the module's last lesson and it does not introduce new tools, with one single specific exception of touch you need for the audit to make sense. Everything else is mkdir/touch (lesson 2), the "list before deleting" discipline (lesson 3), cat and wc -l to measure a file (lesson 4), find (lesson 5), grep (lesson 6), and nano (lesson 7) working together on a realistic-looking case. Module 3 is going to teach you to chain these same commands together with pipes (|); until that happens, every question in this audit gets answered with a standalone command and a careful reading of its output. You are going to feel firsthand, in part 3, why the next module exists.

Inheriting a project is moving into a house with no inventory

When someone leaves a furnished apartment and you move in with no one explaining anything to you, you do not start redecorating. You first walk through every room, check which lights turn on, which keys you were given, and what was left half-fixed in the garage. Only with that list in hand do you decide what to touch first, and how carefully.

Auditing an inherited project is the same, with one important difference: in a house you trust what your eyes see, but in a tree of hundreds or thousands of files your eyes are not enough. You cannot "walk through every room" by opening folder after folder in a file explorer — it is exactly the problem the module's introduction already warned you about. You need commands that walk the tree for you and hand back concrete evidence: not "I think the configuration is around there somewhere," but "the configuration is in these four files, and this is the command that proves it."

The assignment: client-dashboard

You get assigned a project called client-dashboard. The person who maintained it quit two months ago. All you have is this map of its structure, hand-drawn during the handoff meeting:

client-dashboard/
├── README.md
├── .gitignore
├── .env.example
├── notes.txt
├── config/
│   ├── settings.yaml
│   ├── settings.dev.yaml
│   └── logging.conf
├── src/
│   ├── app.py
│   ├── utils.py
│   └── components/
│       ├── header.py
│       └── footer.py
├── tests/
│   ├── test_app.py
│   └── test_utils.py
└── docs/
    ├── setup.md
    └── architecture.md

Your task has three parts: rebuild this tree, fill it with the minimum needed for auditing it to make sense, and audit it with evidence. Before starting, stand in a directory where you do not mind creating and deleting practice things:

cd ~
mkdir -p practice
cd practice

Part 1 — Rebuild the tree with the fewest commands

The challenge is literal: build exactly the tree above using as few commands as possible. mkdir and touch are separate programs — you cannot merge them into a single line — so the real minimum is two commands: one builds all the folders, the other places all the files. The tool that makes this possible is the brace expansion you saw in lesson 2, nested form included.

Step 1: the folders

mkdir -p client-dashboard/{config,src/components,tests,docs}

Anatomy. Bash expands {config,src/components,tests,docs} into four paths before mkdir ever sees them: config, src/components, tests, and docs, each prefixed with client-dashboard/. There is no need to list src separately: -p automatically creates any missing intermediate folder, so asking for src/components gets you both src and src/components in the same step.

Verify with find and the type filter you already know:

find client-dashboard -type d

What to expect (the exact order may vary depending on your system; what matters is that all six paths show up):

client-dashboard
client-dashboard/config
client-dashboard/tests
client-dashboard/docs
client-dashboard/src
client-dashboard/src/components

Step 2: the files

This is where nested expansion does the heavy lifting: you can describe files living in different folders within a single touch call.

touch client-dashboard/{README.md,.gitignore,.env.example,notes.txt,config/{settings.yaml,settings.dev.yaml,logging.conf},src/{app.py,utils.py,components/{header.py,footer.py}},tests/{test_app.py,test_utils.py},docs/{setup.md,architecture.md}}

Anatomy. Read it from the outside in. The outer brace has eight elements separated by commas at the top level: four standalone files (README.md, .gitignore, .env.example, notes.txt) and four groups with their own inner brace (config/{...}, src/{...}, tests/{...}, docs/{...}). Inside src/{...} there is, in turn, another nested brace for components/{header.py,footer.py}. Bash resolves the braces from the inside out and hands you fifteen full paths, all with the client-dashboard/ prefix applied just once.

Verify:

find client-dashboard -type f

What to expect:

client-dashboard/notes.txt
client-dashboard/config/settings.yaml
client-dashboard/config/logging.conf
client-dashboard/config/settings.dev.yaml
client-dashboard/tests/test_utils.py
client-dashboard/tests/test_app.py
client-dashboard/docs/architecture.md
client-dashboard/docs/setup.md
client-dashboard/README.md
client-dashboard/.gitignore
client-dashboard/.env.example
client-dashboard/src/components/header.py
client-dashboard/src/components/footer.py
client-dashboard/src/utils.py
client-dashboard/src/app.py

Fifteen files, two commands. Writing each touch file.ext separately would have been fifteen calls; with well-thought-out nested braces, two. That difference — from fifteen to two — is exactly the kind of return per minute invested the module's introduction promised.

Part 2 — Adding real content so the audit makes sense

Right now your tree is technically correct and useless for practice. Every file is empty and every file has this exact moment's date, so any question about "what changed recently" or "which file is biggest" would have a trivial, worthless answer. Let us fill it with the minimum needed for the audit's five questions to have a real answer, as if the project genuinely had months of work behind it.

Writing the content with nano

You are going to use nano, which you saw in the previous lesson: open it with nano <path>, paste each block's content, save with Ctrl+O followed by Enter, and quit with Ctrl+X. Repeat this for the following six files. The rest of the tree — the remaining nine paths — stays exactly as touch left it: empty. That is intentional, and one of the audit questions depends on it.

client-dashboard/README.md:

nano client-dashboard/README.md
# Client Dashboard

Internal panel for viewing customer metrics.

## Installation

Install the dependencies and run the development server.

## Status

Inherited project, under maintenance.

client-dashboard/src/app.py (code in English, comments in Spanish in the original guide's es/ version — here in en/ both are in English, as with the rest of this catalog):

from utils import calculate_discount, format_currency


def build_invoice(order):
    # TODO: validate that order has the required fields before processing
    subtotal = sum(item["price"] * item["quantity"] for item in order["items"])
    discount = calculate_discount(subtotal, order.get("customer_tier"))
    total = subtotal - discount
    return {
        "subtotal": format_currency(subtotal),
        "discount": format_currency(discount),
        "total": format_currency(total),
    }


def send_invoice(invoice, email):
    # TODO: connect to the real email delivery provider
    print(f"Sending invoice to {email}: {invoice}")


if __name__ == "__main__":
    sample_order = {
        "items": [{"price": 10, "quantity": 2}],
        "customer_tier": "gold",
    }
    invoice = build_invoice(sample_order)
    send_invoice(invoice, "client@example.com")

client-dashboard/src/utils.py:

DISCOUNT_RATES = {
    "gold": 0.15,
    "silver": 0.10,
    "bronze": 0.05,
}


def calculate_discount(subtotal, tier):
    rate = DISCOUNT_RATES.get(tier, 0)
    return subtotal * rate


def format_currency(amount):
    # TODO: support currencies other than USD
    return f"${amount:.2f}"

client-dashboard/config/settings.yaml:

app:
  name: client-dashboard
  environment: production
  debug: false

database:
  host: db.internal
  port: 5432
  name: dashboard

logging:
  level: INFO

client-dashboard/docs/architecture.md:

# Architecture

The dashboard follows a three-layer architecture: `src/` for business
logic, `config/` for per-environment configuration, and `tests/` for the
test suite.

## Components

- `app.py`: builds invoices and sends them.
- `utils.py`: discount calculation and currency formatting.

<!-- TODO: add component diagram -->

client-dashboard/notes.txt (this is the longest one: the complete handoff notes left by the person who quit; paste them as-is, header included):

Handoff notes - client-dashboard
======================================

2026-02-03
First meeting with the support team. The dashboard is used by three
people from the accounts area to check each client's billing status
before the weekly call.

2026-02-10
The tier-based discount calculation (gold/silver/bronze) lives in
src/utils.py. There is no validation if the tier does not exist in
the dictionary; for now the default is 0, which is reasonable but
is not documented anywhere except in the code itself.

2026-02-17
Sending the invoice by email (src/app.py, send_invoice) is still
just a console print. There is no real integration with an email
provider. Before shipping this to production someone has to
connect that to whatever provider the platform team uses.

2026-02-24
Configuration lives in config/settings.yaml for the production
environment. config/settings.dev.yaml also exists but was left
empty; nobody got around to writing the development version.

2026-03-03
config/logging.conf was also left empty. Real logging still uses
the library's defaults, with no per-module levels configured.

2026-03-10
The tests (tests/test_app.py and tests/test_utils.py) are empty
files. They were created as a reminder that they are missing, but
nobody has written a single case yet. This is the first thing to
tackle before touching the discount logic.

2026-03-17
src/components/header.py and footer.py are also empty. The
original idea was to separate the rendering of the dashboard's
header and footer, but the coworker who left never got to that part.

2026-03-24
docs/setup.md was left empty. There are no installation
instructions beyond what the README says, which is generic.

2026-03-31
Sprint closing meeting. It is decided to freeze new feature
development until someone audits the project's real state: what
is done, what is halfway done, and what is just an empty file
waiting for content.

2026-04-07
Personal note: check whether .env.example reflects the variables
config/settings.yaml actually uses. At a glance at least two are
missing: the database URL and the email provider's key.

2026-04-14
Second meeting with the support team. They ask for a report of
gold-tier clients above a certain amount. Noted as pending, not
implemented yet.

2026-04-21
The .gitignore exists but it was never checked whether it excludes
what a typical Python project needs: __pycache__, virtual
environments, real .env files (not .env.example, which is versioned).

2026-04-28
It is noted that docs/architecture.md describes three layers (src,
config, tests) but the diagram mentioned there was never drawn.
It is flagged in the document itself.

2026-05-05
The coworker who owned this project says they are leaving the
company in two weeks. From here on the notes get terser.

2026-05-12
Last relevant commit before leaving: minor formatting tweaks in
src/app.py. No logic changes.

2026-05-19
Formal handoff. These notes remain as the only record of context
for the project for whoever inherits it.

End of notes.

Simulating the project's real age with touch -t

Right now the six files you just wrote — and the nine that stayed empty — all have today's date, because that was the moment touch and nano last touched them. But the notes you just pasted tell a story that starts in February and ends in May. For the audit question about "what changed recently" to have a meaningful answer, you need the filesystem's dates to match that story.

touch accepts the -t option to set an exact date and time instead of "now." Its format is [[CC]YY]MMDDhhmm: two century digits (optional), two for the year, two for the month, two for the day, two for the hour, and two for the minute, all stuck together with no separators. It is one of the few touch options that works identically on macOS and Linux, so there is no fine print involved.

Run this whole block. Each line backdates a file to the date the notes say it was last touched:

touch -t 202602030900 client-dashboard/.gitignore
touch -t 202602100900 client-dashboard/src/utils.py
touch -t 202602240900 client-dashboard/config/settings.dev.yaml
touch -t 202603030900 client-dashboard/config/logging.conf
touch -t 202603100900 client-dashboard/tests/test_app.py
touch -t 202603100900 client-dashboard/tests/test_utils.py
touch -t 202603170900 client-dashboard/src/components/header.py
touch -t 202603170900 client-dashboard/src/components/footer.py
touch -t 202603240900 client-dashboard/docs/setup.md
touch -t 202604070900 client-dashboard/.env.example
touch -t 202604280900 client-dashboard/docs/architecture.md
touch -t 202605120900 client-dashboard/src/app.py
touch -t 202605190900 client-dashboard/notes.txt

Two files deliberately stay untouched: README.md and config/settings.yaml. They keep today's date, as if someone on the team had updated them last week, right before the project landed in your hands. That is what you are going to discover in part 3.

Part 3 — The audit: five questions with evidence

The deliverable rule is always the same: question, command, result. It is not enough to run a command and look at it; you have to be able to point to exactly what you typed and what you got. Always work standing in the directory that contains client-dashboard (not inside it), so the output paths are readable.

Question 1 — Where are all the configuration files?

First define the criterion, in writing, before running anything: you are going to consider "configuration" any file with a .yaml, .yml, or .conf extension, or whose name starts with .env. Notice what does not fit that definition: .gitignore does not count, because it configures git, not the application, and git is out of this guide's scope.

find client-dashboard -iname "*.yaml" -o -iname "*.yml" -o -iname "*.conf" -o -iname ".env*"

What to expect:

client-dashboard/config/settings.yaml
client-dashboard/config/logging.conf
client-dashboard/config/settings.dev.yaml
client-dashboard/.env.example

Interpretation: four files, three of them grouped in config/ and one standalone at the root. Note this command does not use -type f: in this tree it works out the same because no folder is named *.yaml, but if you ever mix -type f with several conditions joined by -or you are going to need to group them with escaped parentheses (\( ... \)) so precedence does not betray you. You are going to need this once this guide teaches you longer expressions; for now, the plain -iname list is enough.

Question 2 — Which files were modified in the last seven days?

find client-dashboard -type f -mtime -7

What to expect:

client-dashboard/config/settings.yaml
client-dashboard/README.md

And to confirm the rest really got left out:

find client-dashboard -type f -mtime +7

What to expect:

client-dashboard/notes.txt
client-dashboard/config/logging.conf
client-dashboard/config/settings.dev.yaml
client-dashboard/tests/test_utils.py
client-dashboard/tests/test_app.py
client-dashboard/docs/architecture.md
client-dashboard/docs/setup.md
client-dashboard/.gitignore
client-dashboard/.env.example
client-dashboard/src/components/header.py
client-dashboard/src/components/footer.py
client-dashboard/src/utils.py
client-dashboard/src/app.py

Interpretation: two recent files, thirteen older than a week. README.md and settings.yaml are precisely the two you left un-backdated in part 2 — the evidence matches the story you built. In a real inherited project, this result would tell you something concrete and actionable: someone touched the production configuration and the README last week, with no record of it anywhere except this date. It is the first question you would ask your team.

Question 3 — Which lines mention the word TODO, and how many are there?

With no pipes yet, grep can answer both halves of this question with two different flags: -n gives you the exact lines, -c gives you a per-file count.

grep -rn "TODO" client-dashboard

What to expect:

client-dashboard/docs/architecture.md:12:<!-- TODO: add component diagram -->
client-dashboard/src/utils.py:14:    # TODO: support currencies other than USD
client-dashboard/src/app.py:5:    # TODO: validate that order has the required fields before processing
client-dashboard/src/app.py:17:    # TODO: connect to the real email delivery provider

Four lines, in three files. For the per-file count:

grep -rc "TODO" client-dashboard

What to expect:

client-dashboard/notes.txt:0
client-dashboard/.gitignore:0
client-dashboard/.env.example:0
client-dashboard/README.md:0
client-dashboard/config/settings.yaml:0
client-dashboard/config/settings.dev.yaml:0
client-dashboard/config/logging.conf:0
client-dashboard/tests/test_utils.py:0
client-dashboard/docs/setup.md:0
client-dashboard/tests/test_app.py:0
client-dashboard/docs/architecture.md:1
client-dashboard/src/components/footer.py:0
client-dashboard/src/utils.py:1
client-dashboard/src/components/header.py:0
client-dashboard/src/app.py:2

Interpretation: grep -rc with -r prints one line for every file it walked through, including the ones with zero matches — not just the ones that matched. With no pipes or awk yet, you add up the total by hand reading the right-hand column: 2 + 1 + 1 = 4, which matches exactly the four lines -n showed you. Once module 3 teaches you | and the text toolkit, you are going to be able to get this same total without adding anything up yourself.

Question 4 — Which is the biggest file, and how many lines does it have?

You do not have sort or pipes yet to sort by size in one shot, so you are going to lean on something you already know: narrowing with -size, same as you did in lesson 5. The technique is raising the threshold until only one file is left.

find client-dashboard -type f -size +500c

What to expect:

client-dashboard/notes.txt
client-dashboard/src/app.py

Still two candidates. Raise the threshold:

find client-dashboard -type f -size +1k

What to expect:

client-dashboard/notes.txt

A single file. Now measure its lines with the tool from lesson 4:

wc -l client-dashboard/notes.txt

What to expect:

      83 client-dashboard/notes.txt

Interpretation: notes.txt is the project's biggest file, with 83 lines. Notice the criterion: you used exact bytes (c) instead of rounded kilobytes for the first cut, precisely so you do not depend on how each system rounds units. This narrowing-by-size — start wide, narrow one threshold at a time — is exactly the same discipline you used with -name in lesson 5; only the filter changes.

Question 5 — Which files were left empty?

find client-dashboard -type f -size 0

What to expect:

client-dashboard/config/logging.conf
client-dashboard/config/settings.dev.yaml
client-dashboard/tests/test_utils.py
client-dashboard/tests/test_app.py
client-dashboard/docs/setup.md
client-dashboard/.gitignore
client-dashboard/.env.example
client-dashboard/src/components/header.py
client-dashboard/src/components/footer.py

Interpretation: nine files, all the ones you left with no content in part 2. On a real project, this list is gold: it tells you exactly what is a real file and what is merely a reminder that "this is missing," with no need to open any of the fifteen files one by one.

The deliverable: the audit report

Now write the report. Open it with nano outside client-dashboard/ — for example at ~/practice/audit-client-dashboard.md — because in the next step you are going to delete the entire practice tree, and the report is exactly what you want to keep.

nano ~/practice/audit-client-dashboard.md

The result should look like this:

# Audit of client-dashboard

## 1. Configuration files
Command: find client-dashboard -iname "*.yaml" -o -iname "*.yml" -o -iname "*.conf" -o -iname ".env*"
Result: config/settings.yaml, config/logging.conf, config/settings.dev.yaml, .env.example (4 files)

## 2. Modified in the last 7 days
Command: find client-dashboard -type f -mtime -7
Result: config/settings.yaml, README.md (2 files; the rest are older than 7 days)

## 3. TODO occurrences
Command: grep -rn "TODO" client-dashboard
Result: 4 lines in 3 files (app.py: 2, utils.py: 1, architecture.md: 1)

## 4. Largest file
Command: find client-dashboard -type f -size +1k
Result: notes.txt, 83 lines (wc -l)

## 5. Empty files
Command: find client-dashboard -type f -size 0
Result: 9 files, including both tests and both UI components

This document is the lesson's real deliverable, not the terminal. It is what you send your team before touching a single line of code.

Safe cleanup of the tree

You finished practicing. Now apply exactly the three safety rules from lesson 3, in order.

First, list what you are about to delete and confirm it is what you think it is:

find client-dashboard

Check the output: it has to be the same fifteen file paths plus the six folders you built in part 1, nothing more. Only now, with the list verified, delete:

rm -r client-dashboard

There is no wildcard in that command — it is a literal name, not a pattern — so the second safety rule (do not use wildcards blindly) is satisfied by construction. Confirm it is gone:

find client-dashboard

What to expect: find: client-dashboard: No such file or directory (or just No such file or directory, depending on your system). That error message is confirmation the cleanup worked. Your audit-client-dashboard.md is still intact, one level up.

Common mistakes

Confusing "how many times the word appears" with "how many lines contain it." grep -c counts matching lines, not occurrences. If a single line had TODO TODO, grep -c would count it as 1, not as 2. It is a conceptual mistake, not a syntax one: it comes from assuming -c does arithmetic on the text when in reality it classifies lines. You spot it by comparing -n's output (which shows every line) against -c's (which counts them) on a file with more than one match per line. You fix it by being explicit in your report about what you are counting — lines with TODO, not occurrences of TODO — instead of assuming both are the same thing. This guide does not cover the way to count exact occurrences (grep -o followed by a count), so an honest definition of the criterion is the real solution here, not a more advanced command.

Flipping the sign of -mtime. It is tempting to read -mtime -7 as "files older than 7 days" because the minus sign sounds like "old." It is the opposite: -7 means less than 7 days of difference (recent), +7 means more than 7 days (old). You spot it when the "recent" list hands you thirteen files and the "old" one hands you two — exactly backward from what this project expects. You fix it by remembering the rule: the sign describes a mathematical comparison (less than, greater than), not an idea of "negative age."

A missing comma in a nested brace does not throw an error, it gives a file with the wrong name. If in part 1's touch command you write src/{app.pyutils.py} instead of src/{app.py,utils.py}, bash does not complain: it literally expands to a file named app.pyutils.py. There is no error message because it is syntactically valid, it just is not what you wanted. You spot it by running find client-dashboard -type f right after every touch and counting: if you expected fifteen files and see fourteen plus one with a strange name, that is where the problem is. You fix it by deleting the badly named file and checking the comma before repeating the command.

Exercises

Each exercise uses exactly this lesson's tools on a case different from client-dashboard. Try each one before opening the solution.

Exercise 1: rebuild this tree with the fewest commands

You have this map of a project called blog-engine:

blog-engine/
├── README.md
├── posts/
│   ├── 2026-01-10-hello-world.md
│   └── drafts/
├── templates/
│   ├── base.html
│   └── post.html
└── static/
    ├── css/
    │   └── style.css
    └── img/

Write the commands (one for folders, one for files) that rebuild it completely, including the two folders that stay empty (posts/drafts/ and static/img/).

See solution
mkdir -p blog-engine/{posts/drafts,templates,static/css,static/img}
touch blog-engine/{README.md,posts/2026-01-10-hello-world.md,templates/{base.html,post.html},static/css/style.css}

Why it works: the first command expands to four folder paths; posts/drafts creates posts along the way thanks to -p, and the same happens with static/css and static/img relative to static. The second command places the map's five files using nested braces for templates/{base.html,post.html}; since posts/drafts and static/img do not appear in touch's brace, they stay as empty folders, exactly as the map asks.

Exercise 2: correct a coworker's interpretation

A coworker runs this on a project called inventory and concludes: "-mtime -7 shows me the old files, the ones nobody touched in the last week."

$ find inventory -type f -mtime -7
inventory/receiving.py
inventory/config.yaml

Are they right? If not, what does that result actually mean?

See solution

It is backward. -mtime -7 selects files whose modification happened less than 7 days ago — that is, the recent ones — not the old ones. The two files that showed up (receiving.py and config.yaml) are the ones someone touched in the last week; everything else in inventory is older than seven days. To see the list of old ones, the correct command would be find inventory -type f -mtime +7.

Why it works: the sign in -mtime is a mathematical comparison on the difference in days (less than n, greater than n), not a label for "negative age." Memorizing the rule in those terms avoids the confusion.

Exercise 3: adding up without pipes

You ran this on a project called inventory looking for the word FIXME:

inventory/receiving.py:3
inventory/shipping.py:0
inventory/reports/monthly.py:2
inventory/reports/weekly.py:0
inventory/utils.py:1

Without using | or awk (still out of scope), answer: how many lines with FIXME are there in total in the project? In how many different files does at least one show up? Which file has the most?

See solution
  • Total lines with FIXME: you add up the right-hand column by hand: 3 + 0 + 2 + 0 + 1 = 6.
  • Files with at least one match: the ones with a number greater than 0: receiving.py, monthly.py, and utils.py3 files.
  • The one with the most: receiving.py, with 3.

Why it works: grep -rc gives you a per-file count, zeros included; with no pipes to add automatically, reading that column by hand is the only method available with what you know up to this module, and it is exactly the same method you used in question 3 of the project.

Exercise 4: isolate the largest file in a single filter

A data/ directory has these five files and sizes:

FileSize
raw_export.csv45,000 bytes
log.txt12,000 bytes
summary.txt800 bytes
notes.md120 bytes
config.json95 bytes

Write a single find -size command that returns exclusively raw_export.csv, and explain why you chose that threshold and that unit.

See solution
find data -type f -size +20000c

Why it works: the threshold has to fall strictly between the second-largest file (log.txt, 12,000 bytes) and the largest one (raw_export.csv, 45,000 bytes); 20,000 meets that condition with plenty of margin. The c suffix (exact bytes) is used instead of k so as not to depend on how each system rounds kilobytes when comparing: with exact bytes the result is identical on any machine.

Summary and next step

Before moving on you should be able to:

  • Rebuild a complete directory tree from a map, with the fewest commands, combining nested braces in mkdir -p and touch.
  • Define a search's criterion in writing (what counts as "configuration," what counts as "recent") before running the command that solves it.
  • Combine find and grep to answer concrete questions about a project you do not know, with reproducible evidence instead of impressions.
  • Apply lesson 3's "list before deleting" rule as a mandatory close to any practice session, not an optional step.

This module started with the promise that you would be able to create structure, read files that do not fit on screen, and find a needle in a haystack of thousands of files. What you just did is exactly that, all together, on a case shaped like real work: you rebuilt, filled, measured, searched, and cleaned up, with evidence at every step.

You also ran into the honest limit of what you know so far. You added up a column of numbers by hand because you had nothing to add it up for you; you narrowed a file's size by trying thresholds instead of asking the machine to sort the list for you. That manual effort was not a flaw in this lesson: it is exactly module 3's motivation, where you are going to chain find, grep, sort, and wc together with | and solve these same five questions in a fraction of the keystrokes you used today.

Resources