Working with Files · about 40 min

Copying, moving and removing

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:

Directories need -r (recursive); without it: cp: -r not specified; omitting directory 'config'.

Useful options:

OptionEffect
-rcopy directories recursively
-iask before overwriting
-nnever overwrite
-vprint each file copied
-ppreserve mode, ownership and timestamps
-aarchive: -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

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:

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

Recap

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.

Exercises

Exercise 1 · quiz

Why is mv instant?

Moving a 4 GB video file with mv from /home/ana/Downloads to /home/ana/Videos (same disk) finishes instantly, but copying it with cp takes a minute. Why?

Answer
  • Within one filesystem, mv only changes directory entries (the name to inode mapping); the data blocks are not touched.

On the same filesystem, mv asks the kernel for a rename: add a name for the inode in the new directory and remove the old name. The inode number, the timestamps and the data stay the same. cp has to create a new inode and copy every byte. Across filesystems (say, to a USB stick) a rename is impossible, so mv falls back to copying and then deleting, and becomes just as slow.

Exercise 2 · quiz

Which commands are dangerous?

You are in /work/project and want to remove the directory build and everything in it. Which commands do that and nothing else? Select all that apply.

Answer
  • rm -r build

  • rm -rf ./build

rm -r build and rm -rf ./build remove the directory tree (-f additionally suppresses questions and errors). rmdir only removes empty directories, so it fails with Directory not empty. rm -r build/ * has a stray space: bash expands * to every name in the current directory, so it deletes the whole project. Always read an rm line twice before pressing Enter.

Exercise 3 · task in a shell

Tidy up a messy directory

/work/inbox is a mess. Organize it like this, using mkdir, mv, cp and rm:

  1. Create inbox/images and move every .jpg and .png file into it.
  2. Create inbox/docs and move every .pdf file into it.
  3. Rename inbox/draft-FINAL-v2.txt to inbox/report.txt.
  4. Make a backup copy of the whole inbox/config directory, named inbox/config.bak (with all its contents; the original stays).
  5. Delete every file ending in .tmp, and delete the empty directory inbox/old.

You may move the files one by one, or use the wildcard *.jpg (all names ending in .jpg); the next lessons explain wildcards properly.

Hint 1

mkdir images docs, then mv beach.jpg cat.png sunset.jpg images/ (or mv *.jpg *.png images/).

Hint 2

Renaming is mv old new. Copying a directory needs cp -r. Empty directories go with rmdir.

Hint 3

From inside inbox: mv draft-FINAL-v2.txt report.txt, cp -r config config.bak, rm *.tmp, rmdir old.

Reference solution
cd inbox
mkdir images docs
mv *.jpg *.png images/
mv *.pdf docs/
mv draft-FINAL-v2.txt report.txt
cp -r config config.bak
rm *.tmp
rmdir old

Exercise 4 · quiz

Into, or as?

/work/a is a directory containing x.txt. /work/b does not exist. You run cp -r a b from /work, and then, a second time, exactly the same cp -r a b. Where is x.txt copied to on the second run? Type the path relative to /work.

Answer

The first run creates b as a copy of a, so you get b/x.txt. On the second run b already exists and is a directory, so cp copies a into it: b/a/x.txt. The same rule applies to mv. The meaning of the last argument depends on whether it is an existing directory. GNU cp and mv have -T (treat the destination as a plain name, never a directory to go into) and -t DIR (always copy into DIR) to remove this ambiguity in scripts.