Lesson 08 / 20
Deletion and Irreversibility
Defining deletion as unlinking, the effect of open file descriptors, verification habits for destructive commands, and the danger of filenames starting with a dash.
Contents
The previous lesson’s commands could lose data too, but all of them did it by overwriting something. Deletion destroys directly, and the file system has no “undo” operation: a file deleted from the command line does not go to a trash can.
This lesson first defines what deletion is at the kernel level — the definition explains why a deleted file can sometimes still be read — and then establishes the verification habits to apply with destructive commands.
Deleting Is Unlinking
What the rm command does is narrower than its name suggests: it removes the
directory entry. The name of this operation at the system call level is unlink.
Every file’s metadata keeps a link count: the number of directory entries
pointing to the file. rm decreases this count by one. The data blocks are freed
only when both conditions are met:
- The link count has dropped to zero.
- No process is left holding the file open.
The second condition can be shown directly:
$ printf 'temporary data\n' > /tmp/temp.txt $ exec 3< /tmp/temp.txt $ rm /tmp/temp.txt $ ls -l /tmp/temp.txt ls: cannot access '/tmp/temp.txt': No such file or directory $ cat <&3 temporary data $ exec 3<&-
The file has been deleted: its name is not in the directory, and listing gives an error. Yet its content could still be read, because a file descriptor is holding it open. The moment the descriptor closes, the data actually becomes free.
This behavior has two practical consequences. First, when a running program’s log file is deleted, the program keeps writing and the disk space is not reclaimed; the space is freed only when the program restarts. Second, deleting a file a program is using does not crash it — the program uses the file by its descriptor, not by its name.
If the link count is greater than one, deletion does not touch the data at all; only one of the names goes. How this is possible is the subject of the next lesson.
Deleting Directories
Directories are deleted with two commands, and the difference between them is a safety criterion.
rmdir deletes only empty directories:
$ ls archive raw-p raw-backup $ rmdir archive/raw-p rmdir: failed to remove 'archive/raw-p': Directory not empty
rm -r deletes a directory together with its contents, recursively:
$ rm -r archive/raw-p $ ls archive raw-backup
This removes the second backup that was created in the previous lesson to
demonstrate the effect of the -p option; the raw-backup directory continues to
exist.
rmdir failing is not a flaw, it is a feature: when it reports that the directory
is not empty, it announces the presence of content that was not intended to be
deleted. Using rmdir to verify that a directory has been emptied is safer than
using rm -r.
What -f Does and Does Not Do
rm gives an error for a nonexistent file and returns a non-zero status:
$ rm /tmp/demo2/missing.txt rm: cannot remove '/tmp/demo2/missing.txt': No such file or directory $ echo $? 1 $ rm -f /tmp/demo2/missing.txt $ echo $? 0
The -f option does two things: it gives no error for nonexistent files, and it
does not ask for confirmation. It does not increase the power to delete — it does
not delete a file that cannot be deleted; permissions still apply.
-f is used often in scripts because the “fine if it is already gone” behavior is
needed. But because it also turns off confirmation questions, combined with -r it
becomes the most dangerous form.
Verification Habits
A fact established in the shell lesson turns into a safety tool here: expansion is done by the shell, the command only sees the result. So the result can be seen before the command is run.
$ echo data/raw/*.csv data/raw/measurement-01.csv data/raw/measurement-02.csv data/raw/measurement-03.csv $ echo rm data/raw/*.csv rm data/raw/measurement-01.csv data/raw/measurement-02.csv data/raw/measurement-03.csv
The second line is the exact form of the command that will run. Putting echo
before a deletion command and reading the output lets you see a misspelled pattern
before paying its price. Running a destructive command this way once is a
one-step check that should become a habit.
Testing the pattern with ls does the same job and also verifies that the files
actually exist.
The shell’s word-splitting rule becomes critical here. If rm data/raw/*.csv is
mistakenly written as rm data/raw/* .csv — a space between the star and the dot —
the pattern matches every file and the directory is emptied. The difference between
the two commands is a single space; the echo pre-check shows this difference
before it produces a result.
rm offers two confirmation options for interactive use:
$ rm -i normal.txt rm: remove regular empty file 'normal.txt'? n $ rm -I *.txt rm: remove 5 arguments? n
-i asks separately for every file and is defined in POSIX. -I asks once, only
when more than three files are given or -r is used; it is specific to GNU tools
and is not found in BSD tools. -I is more usable in practice: being asked about
every single file trains the user to answer “yes” automatically, while a single
question is actually read.
Names Starting with a Dash
Why the -- separator introduced in the shell lesson is necessary becomes visible
here:
$ ls -log.txt normal.txt $ rm -log.txt rm: invalid option -- 'l' Try 'rm ./-log.txt' to remove the file '-log.txt'. Try 'rm --help' for more information. $ rm -- -log.txt $ ls normal.txt
The command has taken the argument starting with a dash for an option. Everything
after the -- separator is always interpreted as a filename; the ./ prefix does
the same job, because the name no longer starts with a dash.
This is not a theoretical problem. A pattern matching such a name can cause a
command to run with unexpected options. In scripts, filenames coming from the user
should always be given after a -- separator.
The Most Destructive Form
The rm -rf combination deletes everything under the given path, without asking
and without reporting errors. Run on the root directory, it targets the entire
system.
This command has not been run in this lesson and must not be tried. Common tool
implementations have a guard for the root directory and the command is refused, but
the guard can be disabled with an option and is not present on every system. The
real danger is not even the root itself: with an empty variable, a line like
rm -rf "$dir"/ targets the root directory.
In practice, three habits prevent most mistakes of this class.
Working with a relative path inside the directory instead of an absolute path. In the wrong directory, the command either gives an error or affects only a narrow area.
Running echo before rm on lines with variable paths. If the variable is
empty, this shows up immediately in the output.
Moving instead of deleting. Moving content aside before deleting it keeps the decision reversible; the moved directory is deleted deliberately after a while.
The only reliable system-level way to recover deleted data is a backup. Backup and snapshot strategies are the subject of the System Administration course; there is no undo within the scope of this course.
Summary
- Deletion removes the directory entry and decreases the link count; data is not freed until the count reaches zero and no process still holds the file open.
rmdirdeletes only empty directories, and this failure is a safety criterion;rm -rdeletes it together with its contents.-fturns off confirmation and the error for a nonexistent file; it does not grant the authority to delete.- Putting
echobefore a destructive command shows the argument list the shell will produce, without producing the result. -iasks once per file,-Iasks once for a batch operation;-Iis not portable.- Filenames starting with a dash are mistaken for options; the
--separator or the./prefix removes this.
Next Step
The definition of deletion left a question open: how can the link count be greater than one, that is, how can a file have more than one name? The next lesson answers this question and compares two different kinds of link — the hard link, which shares the same inode, and the symbolic link, which holds a path string.
To keep your progress and take notes, Log in
My notes
Log in to take notes.