Why this matters
Reorganizing files, making a quick backup before editing a config, cleaning up build output: these are daily jobs, and they are also where people lose work. The shell does exactly what you say, immediately, with no trash can and no undo. Knowing precisely what cp, mv and rm do, and what they do underneath, is what lets you use them quickly and safely.
cp: copy
$ cp notes.txt notes.bak # copy a file to a new name
$ cp notes.txt poem.txt backup/ # copy several files into a directory
$ cp -r config config.bak # copy a directory and everything below it
The rules of the last argument:
- With two arguments where the second is not an existing directory, cp makes a copy with that name (replacing a file of that name if there is one, silently).
- If the last argument is an existing directory, every source is copied into it, keeping its name.
Directories need -r (recursive); without it: cp: -r not specified; omitting directory 'config'.
Useful options:
| Option | Effect |
|---|---|
-r | copy directories recursively |
-i | ask before overwriting |
-n | never overwrite |
-v | print each file copied |
-p | preserve mode, ownership and timestamps |
-a | archive: -r plus preserve everything, symlinks as symlinks. Best for backups |
A plain cp gives the copy the current time as its modification time. cp -a keeps the original's, which matters for backups and for tools that compare times.
mv: move and rename
mv follows the same "last argument" rules, but nothing is left behind:
$ mv draft.txt report.txt # rename
$ mv report.txt docs/ # move into a directory
$ mv *.jpg *.png images/ # move many files into a directory
$ mv docs documentation # rename a directory (no -r needed)
There is no separate rename command for single files; renaming is moving within the same directory.
What happens underneath
Recall from the previous module: a directory is a table of names pointing to inodes. Within one filesystem, mv is a single rename system call that edits those tables:
before after mv Downloads/film.mkv Videos/
Downloads/ Downloads/
film.mkv ──▶ inode 5521 (entry removed)
Videos/ Videos/
(empty) film.mkv ──▶ inode 5521 (same inode, same data)
No data is copied, so moving a huge file or a directory tree is instant, and the inode number and timestamps stay the same (check with ls -i before and after). A rename is also atomic: any other program sees either the old name or the new one, never a half-moved file. Scripts use this to update a file safely: write config.new, then mv config.new config.
When the destination is on a different filesystem, a rename is impossible (inodes belong to one filesystem). mv then copies the data and deletes the original, which is slow and not atomic.
rm: remove
$ rm cache1.tmp cache2.tmp
$ rm -i important.txt
rm: remove regular file 'important.txt'? n
$ rm -r build
$ rmdir old # only if empty
rmdeletes files.rm -rdeletes directories and everything inside them.-iasks for each file;-Iasks once before removing more than three files or recursing.-f(force) never asks and ignores missing files.rm -rfis the "I really mean it" command; it deserves respect.rmdirremoves only empty directories, which makes it a safe choice when you believe a directory is empty: if it is not, it refuses.
There is no trash can
rm asks the kernel to unlink the name: remove the entry from the directory. When an inode has no names left and no process has it open, the kernel frees its blocks. The data is not moved anywhere; it is simply gone. Desktop "trash" folders are a feature of graphical file managers, which really do mv into a hidden directory. The shell has nothing like that.
Habits that prevent disasters:
- Run
lswith the same arguments first:ls *.tmp, thenrm *.tmp. - Use
rm -iorrm -Iwhen removing by pattern. - Never run
rm -rwith a path built from a variable that might be empty (module 12 shows howrm -rf "$dir/"*with an empty$dirbecomesrm -rf /*). - Make a copy first:
cp -a project project.bak.
Overwriting is silent
Both cp and mv replace an existing destination file without asking:
$ echo old > a.txt; echo new > b.txt
$ mv b.txt a.txt
$ cat a.txt
new
The old a.txt is gone. Add -i (ask) or -n (no clobber) when that matters. Some people put alias cp='cp -i' mv='mv -i' rm='rm -i' in their ~/.bashrc. It helps, but it builds a habit that fails you on machines without the alias, and aliases do not apply in scripts.
Open the shell and try
Open the shell for this lesson. Run tree inbox to see the mess you will tidy in the task, then experiment freely in a scratch directory:
$ mkdir play && cd play
$ echo one > a.txt
$ ls -i a.txt
16502 a.txt
$ mv a.txt b.txt; ls -i b.txt
16502 b.txt
$ cp b.txt c.txt; ls -i c.txt
16503 c.txt
Same inode after mv, a new inode after cp: the underneath story, visible.
Common mistakes
cp dir destwithout-r. cp skips directories with-r not specified; omitting directory.- The "into vs as" surprise.
cp -r a bcreatesbthe first time andb/athe second.mvbehaves the same way. - A stray space in an
rmline.rm -r build/ *removes everything. Read before Enter. - Assuming you can undo. There is no trash can and no
undocommand. - Losing timestamps in backups. Use
cp -a, not plaincp -r. - Moving across disks and expecting speed. Different filesystems mean copy plus delete.
Recap
cp src destcopies;-rfor directories;-afor faithful backups.mvrenames and moves; within a filesystem it is an atomicrenamethat never touches the data.rmunlinks names; data is freed when no names remain. No trash, no undo.rmdirremoves only empty directories.- If the last argument is an existing directory, cp and mv go into it.
- cp and mv overwrite silently unless you use
-ior-n.
Going further: look at cp --help for --backup (keep numbered backups of overwritten files) and try mv -v --backup=numbered x.txt y.txt twice.