A safer rm

Alternatives to the venerable rm command line utility.

I spend a lot of time in the terminal, which means deleting files and directories with rm. However, rm isn't fussy about what it deletes and doesn't believe in second chances. For interactive use, I think we can do a bit better. In this article, I'll first discuss some safer alternatives to rm and then offer my current preferred solution.

Alternatives

One of the older (though still maintained!) efforts to improve the safety of rm is safe-rm, which is a simple wrapper around rm that refuses to delete a list of protected directories. By default rm will refuse to remove the root of the filesystem, /, but that's it. In contrast, safe-rm refuses to remove a list of important system directories, such as /bin and /usr. Additional directories can be protected by listing them in a configuration file. safe-rm was originally a Perl script but has recently been rewritten in Rust, and is meant to be a drop-in replacement for rm.

The key feature of the subsequent alternatives is that files are moved to some trash directory instead of being immediately deleted, much like typical desktop GUI trash programs. This allows files deleted by mistake to be restored until the trash is permanently deleted later.

One example is shell-safe-rm, which is a bash script that aims to be an almost-complete drop-in replacement for rm, except that it moves files to the directory ~/.Trash. The tool does not provide a built-in mechanism for restoring or emptying the trash, leaving this for the user to do manually.

Another example is rip, which is written in Rust. rip is intended to be an intuitive and ergonomic alternative to rm rather than a drop-in replacement. In particular, rip doesn't bother with -r or other flag to remove directories. Like shell-safe-rm, it moves deleted files to a particular directory. By default, rip uses a directory in /tmp, so that the deleted files are automatically cleaned up when the computer reboots. I'm a little hesitant about this, in case my computer crashes or otherwise reboots before I realize a file was removed by mistake; I want a more predictable process for permanent deletion. Luckily, rip allows the destination for deleted files to be easily changed through environment variables or a command line argument.

Finally, a rather attractive prospect is to follow the same specification as common Unix desktop GUI trash programs, the XDG trash spec. A popular CLI tool that implements this spec is the Python tool trash-cli. trash-cli provides scripts for all of the utilities expected from a trash program: move files to trash, inspect them, restore them, or delete them permanently.

My preferred alternative

Ultimately, I want a tool with the following features:

  1. Move files to a trash directory rather than immediately deleting permanently. This is typical of GUI trash programs and a main feature of shell-safe-rm, rip, and trash-cli.
  2. Confirmation before deleting multiple items (to avoid, say, an incorrect glob). This is actually present in plain rm: the -i flag prompts before removing each and every file; -I prompts once before removing directories or more than three files.
  3. Refuse to delete a protected list of directories, like safe-rm.
  4. Files are automatically permanently deleted after having been in the trash for N days.

I played around with my own implementation of (1) for a while, but eventually decided to just wrap trash-cli to get (1) and the rest of the XDG trash spec for free. My wrapper just adds features (2) and (3), both of which are quite simple to implement, on top of the trash-put script from trash-cli.

For feature (4), I initially had each call to my wrapper check for files older than 7 days and delete them, which can cause a noticeable delay if there is a lot to delete. A better solution is to set up a cron job to regularly empty the trash automatically. One way to do this is to use the trash-empty script from trash-cli and a crontab entry like

@daily /path/to/trash-empty 30

which runs a daily job to remove files that have been in the trash for at least 30 days.

2023-08-06 update: the @daily cron jobs are handled by anacron, which by default runs as root and will not properly empty the user's local trash (see this issue). One option is to set up a non-root anacron; another is just to use a normal hourly cron job like

0 * * * * /path/to/trash-empty 30

I'll also mention autotrash, which is a dedicated tool for automatically emptying the trash can. It can set up a systemd service to do so, or can be added as a cron job similar to the one above.

Summary

I discussed four safer versions of rm:

After hacking on my own toy version for a little while, I ended up wrapping trash-cli and using a cron job to automatically remove old trash. If you have an alternative (better?) solution to removing files in the CLI, I'd be happy to hear about it by email or Twitter.