Copy, Move, Delete — and the Habits That Keep You Safe
"The terminal assumes you mean exactly what you say. Deleting is the moment to be sure you do."
In Moving Around you learned to look and to create. Now come the tools that change things: copy, move, rename, delete. They're wonderfully fast — one line does what fifty drag-and-drops
would — and they come with a responsibility the graphical world hid from you: there is no confirmation
dialog, and for rm, no trash can. This part teaches the commands and, just as deliberately,
the habits that make them safe.
ls first), prefer interactive flags (-i) while learning, and preview every
wildcard with echo before letting it loose. Every habit in this part exists because someone, somewhere,
lost a day's work not having it.3.1 Copying
Copying is the gentlest of the three power tools — the original is never touched. It's also
your cheapest insurance policy: one cp before an experiment means there's always
a way back.
config.yaml. If its "improvement" breaks everything, you want the original
back in one command.cp always reads the same way: source first, destination second — "copy this, to there." What "there" means depends on what the destination is:
cp # Destination is a new name: copy + rename in one step
cp config.yaml config.yaml.bak
# Destination is an existing folder: copy INTO it, same name
cp config.yaml backups/cp file file.bak. Restoring later is just the reverse: cp config.yaml.bak config.yaml. (a Git-free time machine, until you have
Git.)Copying Folders Needs -r
Ask cp to copy a directory and it declines — a folder can contain thousands of
files, and cp wants you to say you mean it:
cp -r for directories cp projects backup-projects
# cp: -r not specified; omitting directory 'projects'
cp -r projects backup-projects # -r = recursive: the folder and everything insidecp overwrites silently. If the destination file already
exists, cp replaces it — no question asked, old contents gone. While you're learning,
add -i ("interactive"): cp -i config.yaml backups/ stops and asks overwrite? before clobbering anything — type y or n and press Enter, and
a bare Enter counts as no. That -i belongs to cp, though,
not to the terminal: sed takes the same letter and means something far sharper by it (Editing in Place). This same silent-overwrite rule returns with mv in the next section — it's a theme."Back up my config file before you change anything, and tell me the restore command"
"Copy the whole src folder to src-backup so we can experiment safely"
3.2 Moving & Renaming
Here's a small mind-bender: the terminal has no "rename" command, because it doesn't need
one. Renaming is moving — moving a file to a new name in the same place. One
command, mv, does both.
untitled.py, and dropped three data files in the project's top folder that
belong in data/. Tidy it up.Like cp, it's always source first, destination second — and the
same two shapes:
mv — rename or relocate # Destination is a new name: RENAME
mv untitled.py main.py
# Destination is an existing folder: MOVE into it
mv sales.csv users.csv logs.csv data/
# Both at once: move AND rename
mv draft.md docs/chapter-1.mdThe middle line is the fine print behind "source first, destination second": once you name
more than two things, the last one has to be a folder, and it has to exist already.
Neither cp nor mv will make one for you — with no docs/ yet, the last line stops at No such file or directory, and you fix it with mkdir docs and run it again.
Two nice surprises compared to cp: mv moves whole folders without needing -r (on the same disk it just relabels the folder's address — instant, even for gigabytes), and since
nothing is duplicated, there's no copy lying around to get stale. Move to a different disk, though — an external drive, a USB stick — and mv quietly
becomes a full copy followed by a delete: slow, and interruptible halfway through.
mv replaces it silently — and unlike cp, you lose both versions of the
story: the destination's old contents are gone, and the source no longer exists under its
old name. mv notes.txt ideas.txt when ideas.txt already exists destroys ideas.txt. The fix is the same reflex: mv -i asks before overwriting, and ls the destination first when in doubt."Rename untitled.py to main.py and move every CSV in this folder into data/"
"Reorganize these files into sensible folders — show me the mv commands before running them"
3.3 Deleting (Carefully)
This is the most important safety lesson in the entire course. Everything else in the
terminal is forgiving — errors are harmless, cd can't hurt you, cp leaves originals alone. rm is the exception. Read this section twice.
rm does no such thing: the file is gone the instant you press Enter. No confirmation, no undo, no
recovery. Treat every rm as permanent, because it is.rm family rm old-notes.txt # Delete one file. Permanently.
rm draft1.md draft2.md # Delete several
rmdir empty-folder # Delete a folder — only works if it's EMPTY
rm -r old-project # Delete a folder and everything inside it
rm -f missing.txt # -f = force: no complaints if it doesn't existEach flag is reasonable alone. -r recurses into folders; -f suppresses prompts and "no such file" errors so scripts run clean. Combined as rm -rf, they mean: delete everything at this path, recursively, without asking, without stopping. Now
add the two ingredients from your recent lessons and you have the terminal's most famous
disaster recipe:
rm -rf — no questions
Recursive and forced: nothing slows it down, nothing asks "are you sure?"
+ a wildcard
* expands to "everything here" — however much that turns out to be (next section).
+ the wrong directory
You thought you were in ~/tmp. You were in ~/projects. The pwd habit exists for this moment.
A single mistyped space does it too: rm -rf old-app / (note the stray space) is not "delete old-app/" — it's "delete old-app, and also the entire filesystem starting at root." This exact typo has destroyed
production servers. It's why modern systems refuse plain rm -rf / without an extra flag — but they won't save you from ~ or a wrong folder.
-old.txt jams the grammar from Getting Help: rm -old.txt reads as a fistful of flags, not a filename, and the error complains
about an invalid option instead of the real problem. Two ways out: rm -- -old.txt — the double dash means "everything after this is a name, no matter
what it looks like," and nearly every command honors it — or point at the file with a path, rm ./-old.txt. Worth knowing before some download names itself something dashed.The Ritual: ls First, Then rm
Here's the habit that makes rm boring instead of scary. Never aim rm at anything you haven't just looked at. List first, confirm with your eyes, then recall the command
(up-arrow) and swap ls for rm — same targets, guaranteed:
ls old-project # 1. LOOK: what exactly is in there?
pwd # 2. CONFIRM: am I where I think I am?
rm -r old-project # 3. Same target you just inspected — up-arrow, edit, Enterrm -rf, do not rubber-stamp it. Read three things before approving: the exact path (absolute? relative to what?), any wildcard (what could it expand
to?), and the working directory the agent is running in. Agents make
location mistakes exactly the way beginners do — they just make them faster. Deletion is the
one category of command you always read in full — and Terminal for the AI Era later turns
that instinct into a complete auditing method.rm -i asks before every single deletion, and rm -I (capital i) asks once when deleting more than three files — a good permanent
default. And for anything precious, the calmer move is often mv: relocate to a scrap folder today,
delete the scrap folder next week. The exception is build artifacts — whatever
the project's build leaves behind, the step that turns your source files into something you can
run. Delete those freely; running the build again writes them straight back.Try It: Clean the Downloads Mess
ls and cat, make folders, sort keepers into them with mv, and rm the junk — practicing the ls-first ritual where deletion
can't hurt you.Loading playground...
"You suggested rm -rf — walk me through exactly what that path resolves to before I approve it"
"Delete the build artifacts in this project, but list everything you plan to remove first"
3.4 Wildcards
Everything so far has operated on files one name at a time. Wildcards — also called globs — let one pattern stand for many names: "every .log file,"
"all the photos from January." This is where selecting files by hand in a file manager starts
to feel slow.
.tmp files littered through this folder. Deleting them one by one would take ten
minutes. One pattern does it in a second — if you can trust what the pattern matches.| Pattern | Matches | Example |
|---|---|---|
* | Any run of characters (including none) | *.log → app.log, errors.log |
? | Exactly one character | page?.html → page1.html, not page12.html |
[abc] | One character from the set (ranges like [0-9] work too) | report-[12].txt → report-1.txt, report-2.txt |
The Big Idea: the Shell Expands the Glob, Not the Command
This is the one concept that makes wildcards predictable instead of magical. When you type rm *.tmp, the rm program never sees a star. The shell expands the pattern against the current
directory first, then runs the command with the resulting list of plain filenames:
Two consequences follow directly. First, what a glob matches depends entirely on where you are — the same *.tmp is three files in one folder and three hundred in another (this is the wildcard half of the rm -rf disaster from Deleting (Carefully)). Second, since
expansion happens before any command runs, you can ask a harmless command to show you
the expansion:
echo the glob first echo *.tmp
# cache1.tmp cache2.tmp scratch.tmp <- exactly what rm would receive
rm *.tmp # Now you KNOW what this deletesecho just prints its arguments — so echo <pattern> is a free, safe preview of any wildcard. (ls <pattern> works too; the angle brackets
mark the spot you fill in and are never typed — the convention from Getting Help.) Make it a reflex before every destructive glob, and apply
it to AI-proposed commands: when an agent suggests a command containing a wildcard, echo
that pattern in the target directory before you approve the real thing.Globs compose with everything you already know — they're just a way of writing file lists:
cp, mv, ls cp *.md drafts/ # Copy every Markdown file into drafts/
mv photo-0?.jpg january/ # Move photo-01.jpg through photo-09.jpg
ls report-[12].txt # List just report-1.txt and report-2.txt
ls src/*.js # Globs work inside a path too — but only one folder deepThat last line is where globs stop: one never crosses a /. src/*.js looks inside src and gives up there — it will not find src/lib/util.js, and no arrangement of stars will make it. When a mess is
spread down through subfolders the pattern is the wrong tool, and walking a whole tree is find's job: find . -name '*.tmp' would have caught those 40 files
wherever they'd landed. It has a section waiting in Finding Files.
* does not match hidden dotfiles (a small mercy — rm * spares your .env), and a pattern that matches nothing is passed to
the command literally, star and all — which is why a typo'd glob often produces the baffling
error cannot access '*.tpm'. Both surprises are caught instantly by the echo
habit.mkdir -p src/{components,lib}. Those braces don't match anything
— they expand, unconditionally: the shell rewrites the line into mkdir -p src/components src/lib before mkdir ever runs. A glob
asks "what's already here?"; braces say "make me these." That's why braces work for files that
don't exist yet — exactly where a glob would come back empty-handed — and why coding agents lean
on this shape every time they scaffold a project. You'll read it far more often than you'll type
it.Try It: Select Exactly the Right Files
echo every glob before you commit to it; that's the skill
being tested.Loading playground...
"Write a glob that matches all the 2024 log files but not the 2025 ones, and echo it first"
"Move every image file in this folder into photos/ — show me what the wildcard matches before moving"
Loading challenge...