Remove Empty Directories Safely with rmdir
You will remove empty directories with GNU rmdir, verify what happened, and understand why a non-empty directory is refused. The command removes directories only, never their contents. Allow about ten minutes. You need a shell and write permission on the directory and its parent; none of the examples needs sudo.
The route
Jump straight to the step you need, or tick off Done means at the end.
This guide describes GNU coreutils rmdir 9.4, the version installed on this machine. Check your own version before relying on a detail in a script, because another implementation or release can differ.
1. Check the installed command
Start with read-only checks. They confirm the binary and its package version without changing the filesystem:
$ command -v rmdir
/usr/bin/rmdir
$ rmdir --version
rmdir (GNU coreutils) 9.4
$ dpkg-query -W -f='${Package} ${Version}\n' coreutils
coreutils 9.4-3ubuntu6.1
The exact package revision may differ on your host. The useful facts are that this is the GNU command and that --version reports the implementation you are about to run.
2. Create a disposable empty directory
Use a directory under /tmp for a safe smoke test. The mkdir -p command creates the parent and child in one ordinary, unprivileged operation:
$ test_root=$(mktemp -d)
$ mkdir "$test_root/archive"
$ find "$test_root" -maxdepth 2 -type d -print
/tmp/tmp.example
/tmp/tmp.example/archive
Your temporary path will not be /tmp/tmp.example. Keep the value of test_root in the same shell. Do not replace it with a broad path such as / or your home directory.
Checkpoint
Confirm that the directory you intend to remove is the empty child, not the temporary root:
$ find "$test_root/archive" -mindepth 1 -maxdepth 1 -print
$ rmdir "$test_root/archive"
$ test ! -e "$test_root/archive" && echo "archive removed"
archive removed
An empty result from find means there were no entries below the directory. A successful rmdir normally prints nothing and returns status zero.
3. See the refusal for a non-empty directory
Warning
rmdir is deliberately conservative, but it still changes the filesystem when it succeeds. Put a harmless file in a second test directory and observe the refusal:
$ mkdir "$test_root/with-file"
$ printf '%s\n' 'keep this file' > "$test_root/with-file/notes.txt"
$ rmdir "$test_root/with-file"
rmdir: failed to remove '/tmp/tmp.example/with-file': Directory not empty
$ printf 'exit status: %s\n' "$?"
exit status: 1
The path in the diagnostic will be different on your system. The file remains in place, so this failed command has not removed the directory or its contents:
$ test -f "$test_root/with-file/notes.txt" && echo "file still exists"
file still exists
Do not switch to rm -r merely because rmdir refused. Recursive removal is a different, destructive operation. If the directory is supposed to contain data, inspect and back up that data first. If it is disposable test data, remove the file explicitly only after checking the path, then retry:
$ rm -- "$test_root/with-file/notes.txt"
$ rmdir "$test_root/with-file"
$ test ! -e "$test_root/with-file" && echo "directory removed"
directory removed
The rm command in this example is irreversible unless you have a backup. There is no undo operation built into rmdir; recovery means restoring the directory from a backup and recreating it.
4. Remove empty ancestors with --parents
Use -p or --parents when you want to remove a directory and then its ancestors, stopping at the first ancestor that is not empty or cannot be removed. Create a nested test path:
$ mkdir -p "$test_root/project/cache"
$ mkdir "$test_root/keep"
$ rmdir --parents "$test_root/project/cache"
$ test ! -e "$test_root/project" && echo "empty chain removed"
empty chain removed
The command attempts the named directory first, then its parent, then the next parent. The keep marker makes the temporary root non-empty, so it remains in place and the command stops there. Do not use --parents on a path whose ancestors you need to keep. If an ancestor contains another entry, that ancestor remains and rmdir reports the relevant failure.
The manual describes rmdir -p a/b as similar to rmdir a/b a. That is a useful mental model, but the explicit option makes the intended chain clearer in a reviewed command.
5. Process several directories and read diagnostics
rmdir accepts more than one directory operand. With -v or --verbose, it prints a diagnostic for every directory it processes, which is useful in a maintenance script or a manual cleanup:
$ mkdir -p "$test_root/one" "$test_root/two"
$ rmdir --verbose "$test_root/one" "$test_root/two"
rmdir: removing directory, '/tmp/tmp.example/one'
rmdir: removing directory, '/tmp/tmp.example/two'
Output wording and the temporary path vary, but each successful operand should have a corresponding message. Without -v, success is silent. A non-zero exit status means at least one requested removal failed, so scripts should check $? or use the command's status directly rather than treating silence as proof of success.
--ignore-fail-on-non-empty suppresses failures caused specifically by non-empty directories. Use it only when that condition is an expected result and you will verify other errors separately. It does not turn a non-empty directory into an empty one, and it does not make permission or missing-path errors harmless.
6. Clean up the test area
After the examples, check whether the temporary root still exists. If it is empty, remove it with rmdir:
$ if [ -d "$test_root" ]; then
> rmdir "$test_root" && echo "temporary root removed"
> fi
temporary root removed
If the command reports that the root is not empty, inspect it with find "$test_root" -mindepth 1 -maxdepth 2 -print. Remove only entries you recognise. Leaving a named temporary directory for later inspection is safer than using a recursive wildcard against an uncertain path.
Done means
- You checked that the installed command is GNU coreutils
rmdirand noted its version. - You verified the target directory before removing it.
- You know that ordinary
rmdirrefuses non-empty directories and does not delete their contents. - You used
--parentsonly where removing empty ancestors was intentional. - You checked the exit status or verbose output when processing several paths.
- You inspected or removed the disposable test area without applying recursive deletion to an uncertain path.