ZetCode

Linux rm Command

last modified October 23, 2026

The rm (remove) command deletes files and directories. There is no recycle bin on the command line: rm removes the directory entry immediately, and the space is freed once no other name or process refers to the file. Deleted data is usually gone for good, so it pays to know exactly what a given rm command will match.

The syntax is rm [options] file.... The examples use GNU rm from coreutils 9.1 and Bash 5.2, run in a Debian container. The important options are -r (recursive), -f (force), -i and -I (ask), -d (empty directories), and -v (verbose).

Deleting a file

The simplest form deletes one file.

ls
rm temp.log
ls

Like most Unix commands, rm prints nothing when it succeeds. The second ls shows that the directory is now empty.

$ ls
temp.log
$ rm temp.log
$ ls

Deleting multiple files

Several files can be removed with one command.

rm backup1.tar missing.tar backup3.tar
echo $?
ls

When one of the arguments does not exist, rm reports it, removes the remaining files, and exits with status 1. Only backup2.tar, which was not named, is left.

$ rm backup1.tar missing.tar backup3.tar
rm: cannot remove 'missing.tar': No such file or directory
$ echo $?
1
$ ls
backup2.tar

Deleting empty directories

Without options, rm does not remove directories. Empty directories are removed with rmdir or rm -d.

rm empty_dir
rmdir empty_dir
rmdir old_project
rm -d old_project

Both rmdir and rm -d refuse to delete a directory that has any content, including hidden files. This makes them a safe way to clean up directories that should already be empty.

$ rm empty_dir
rm: cannot remove 'empty_dir': Is a directory
$ rmdir empty_dir
$ rmdir old_project
rmdir: failed to remove 'old_project': Directory not empty
$ rm -d old_project
rm: cannot remove 'old_project': Directory not empty

Deleting directories recursively

The -r (-R, --recursive) option deletes a directory with all its files and subdirectories.

tree -a temp_files
rm -rv temp_files

With -v, every removed file and directory is listed. The contents are deleted first, from the deepest level up, and the directory itself last. Hidden files such as .hidden are deleted too.

$ tree -a temp_files
temp_files
├── a.txt
├── cache
│   ├── b.dat
│   └── thumbs
│       └── c.png
└── .hidden

3 directories, 4 files
$ rm -rv temp_files
removed 'temp_files/cache/b.dat'
removed 'temp_files/cache/thumbs/c.png'
removed directory 'temp_files/cache/thumbs'
removed directory 'temp_files/cache'
removed 'temp_files/.hidden'
removed 'temp_files/a.txt'
removed directory 'temp_files'

Write-protected files and -f

When a file is write-protected and the input is a terminal, rm asks before deleting it. The -f (--force) option removes it without asking.

ls -l config.bak
rm config.bak
ls config.bak
rm -f config.bak
ls config.bak

The answer n keeps the file. With -f, the file is deleted silently. -f also suppresses the error for files that do not exist and makes the exit status 0 in that case. It does not override the permissions of the directory, as shown later.

$ ls -l config.bak
-r--r--r-- 1 jano jano 10 Oct 23 13:22 config.bak
$ rm config.bak
rm: remove write-protected regular file 'config.bak'? n
$ ls config.bak
config.bak
$ rm -f config.bak
$ ls config.bak
ls: cannot access 'config.bak': No such file or directory

Interactive deletion

The -i option asks about every file. The -I option asks once, when more than three files are deleted or when deleting recursively.

rm -i error.log debug.log
ls *.log
rm -I *.tmp
ls *.tmp

The first command deletes error.log and keeps debug.log. The single -I question protects against a mistyped wildcard without the tedium of confirming every file; here, the answer n keeps all four files.

$ rm -i error.log debug.log
rm: remove regular empty file 'error.log'? y
rm: remove regular empty file 'debug.log'? n
$ ls *.log
debug.log
$ rm -I *.tmp
rm: remove 4 arguments? n
$ ls *.tmp
a.tmp
b.tmp
c.tmp
d.tmp

Deleting files with a wildcard

Wildcards are expanded by the shell before rm runs, so the match can be checked with echo first.

echo *.tmp
rm *.tmp
ls
rm *.tmp
rm -f *.tmp
echo $?

The file my notes.tmp is matched correctly; the shell passes it as one argument even though it contains a space. When nothing matches, Bash passes the pattern unchanged, and rm reports that the file *.tmp does not exist. With -f, the error is suppressed. Do not quote the wildcard itself: rm "*.tmp" would look for a file literally named *.tmp.

$ echo *.tmp
file1.tmp my notes.tmp temp.tmp
$ rm *.tmp
$ ls
keep.txt
old_project
$ rm *.tmp
rm: cannot remove '*.tmp': No such file or directory
$ rm -f *.tmp
$ echo $?
0

Hidden files

Files whose names begin with a dot are not matched by *. They need a pattern that starts with a dot.

ls -a
rm .*.swp
ls -a
echo .*
rm -r . ..

The pattern .*.swp removes the Vim swap files. Since Bash 5.2, .* no longer matches . and ..; older Bash versions and other shells expanded it to include them. GNU rm refuses to remove . and .. in any case, but rm -r .* is still a habit to avoid.

$ ls -a
.
..
.bashrc
main.c
.main.c.swp
.notes.txt.swp
$ rm .*.swp
$ ls -a
.
..
.bashrc
main.c
$ echo .*
.bashrc
$ rm -r . ..
rm: refusing to remove '.' or '..' directory: skipping '.'
rm: refusing to remove '.' or '..' directory: skipping '..'

File names starting with a dash

A file name that begins with - is read as an option.

ls
rm -report.txt
rm -- -report.txt
ls

The double dash -- marks the end of options, so everything after it is a file name. As the error message suggests, rm ./-report.txt works too. In scripts, write rm -- "$file" for the same reason.

$ ls
keep.txt
old_project
-report.txt
$ rm -report.txt
rm: invalid option -- 'e'
Try 'rm ./-report.txt' to remove the file '-report.txt'.
Try 'rm --help' for more information.
$ rm -- -report.txt
$ ls
keep.txt
old_project

Permissions needed to delete a file

Deleting a file changes the directory that contains it, not the file. What counts is write permission on the directory.

chmod 555 locked
rm -f locked/file.txt
chmod 755 locked
chmod 000 locked/file.txt
rm -f locked/file.txt
ls locked

In a read-only directory, even rm -f fails. Once the directory is writable, a file with no permissions at all can be deleted. In directories with the sticky bit set, such as /tmp, users can delete only their own files.

$ chmod 555 locked
$ rm -f locked/file.txt
rm: cannot remove 'locked/file.txt': Permission denied
$ chmod 755 locked
$ chmod 000 locked/file.txt
$ rm -f locked/file.txt
$ ls locked

rm removes names

A file can have several names (hard links). rm removes one name; the data stays as long as another name exists.

echo "important" > data.txt
ln data.txt data-link.txt
ls -li data*
rm data.txt
cat data-link.txt

Both names share the same inode number, and the link count is 2. After rm data.txt, the content is still reachable through data-link.txt. For the same reason, the disk space of a deleted file is not freed while a process still has it open, which is common with large log files.

$ echo "important" > data.txt
$ ln data.txt data-link.txt
$ ls -li data*
2628345 -rw-r--r-- 2 jano jano 10 Oct 23 13:22 data-link.txt
2628345 -rw-r--r-- 2 jano jano 10 Oct 23 13:22 data.txt
$ rm data.txt
$ cat data-link.txt
important

Deleting old files with find

To delete files by age, size, or other criteria, let find select them. Always run the command without the deleting part first.

ls -l --time-style=+%F logs
find logs -name "*.log" -mtime +30
find logs -name "*.log" -mtime +30 -delete
ls logs

-mtime +30 matches files modified more than 30 days ago. The first find only prints the matches; the second deletes them with -delete, which is faster and safer than -exec rm {} \; because it does not start a process per file. See the find command article for details.

$ ls -l --time-style=+%F logs
total 0
-rw-r--r-- 1 jano jano 0 2026-10-23 app.log
-rw-r--r-- 1 jano jano 0 2026-08-01 app-old.log
-rw-r--r-- 1 jano jano 0 2026-10-23 db.log
-rw-r--r-- 1 jano jano 0 2026-08-01 db-old.log
-rw-r--r-- 1 jano jano 0 2026-10-23 notes.txt
$ find logs -name "*.log" -mtime +30
logs/db-old.log
logs/app-old.log
$ find logs -name "*.log" -mtime +30 -delete
$ ls logs
app.log
db.log
notes.txt

Guarding variables in rm commands

In scripts, an empty or unset variable can turn a harmless command into a destructive one.

dir=""
echo rm -rf "$dir"/*
rm -rf "${dir:?}"/*
dir=build
rm -rf "${dir:?}"/*
ls -A build

With an empty dir, "$dir"/* becomes /*, and the echo shows that the command would target every directory under the root. The ${dir:?} expansion stops the command, and a non-interactive script, with an error when the variable is empty or unset. With dir=build, the contents of build are deleted as intended. Running scripts with set -u catches unset variables in general. GNU rm also refuses rm -r / unless --no-preserve-root is given, but that does not protect /*.

$ dir=""
$ echo rm -rf "$dir"/*
rm -rf /bin /boot /dev /etc /home /lib /lib64 /media /mnt /opt /proc /root /run /sbin /srv /sys /tmp /usr /var
$ rm -rf "${dir:?}"/*
bash: dir: parameter null or not set
$ dir=build
$ rm -rf "${dir:?}"/*
$ ls -A build

Best practices for rm

Source

GNU rm manual

In this article, we have used the rm command to delete files and directories, handle prompts and write-protected files, deal with tricky file names, and delete files safely in scripts.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Linux tutorials.