2

I had once a dangerous experience with the use of rm -rf, due to a unset variable. Since then, I have been extremely cautious in composing a script that involves rm -rf. Cross checking the spell mistakes, spaces etc. multiple times.

So far, I have been following these practices wherever possible:

  • Using non-root user.
  • Avoid using of *, and using more specific file paths.
  • Avoidig use of variables for directory name.

So I would like to know if there is some standard/recommended practices or approach or a script to use rm -rf, especially with * so that it won't be destructive if there is a some manual mistake in script.

Update:

A work in progress bash utility to empty a given directory is created here at: https://github.com/g1patnaik/system_utilities/tree/dev/empty_directory

3

3 Answers 3

6

I don't think this question is about avoiding rm -rf, but about avoiding ending up in states where the operation is unsafe or using it in ways that could have uncertain consequences. When it comes to spelling errors, you could catch some of them using tools like shellcheck and setting certain shell variables (e.g. failglob in bash, and nounset for example). Still, ultimately it comes down to forming a habit of reading your code before you run it and testing it on throwaway or backed-up data.

I don't see the use of complicating it too much. If you want to delete all the directory contents, it would be the most straightforward approach to delete the directory entirely and recreate it.

rm -rf -- "$directory" && mkdir -p -- "$directory" || exit

Note the quoting of $directory. It is needed because the string in $directory would otherwise be split into terms so that if the variable's value is foo bar, the code would remove the two names foo and bar. The words would additionally be expanded as file name globbing patterns, making it dangerous to remove a directory called literally *. We use the -- to end option parsing before the actual directory name, enabling us to work with directories called, e.g., -f or --version.

The exit above would be executed if either rm or mkdir failed. The code would naturally also reset any ownership and other metadata on the directory it recreates.

Another approach would be using find.

find "$directory/." ! -name . -delete

The above uses the -delete predicate of find, which is non-standard but commonly implemented. It deletes everything beneath the given directory except for the directory itself. In this case, the original directory entry is left in place rather than recreated.

Using portable find, we would write this

find "$directory/." -depth ! -name . -exec rm -rf {} +

Note that your code would execute cd - even if your first cd failed, possibly leaving your script in an unexpected working directory. Your code also fails in removing hidden names and it would fail with "argument list too long" if the globbing pattern expands to too many names. If * does not match anything, it will terminate a script running with the failglob (or equivalent) shell option set (note that this is a good thing if the directory was expected to be non-empty, but was empty).

11
  • 3
    Recreating it means some metadata (ownership, permissions, ACLs, other extended attributes, birth time...) will be lost though. Oct 4, 2021 at 13:35
  • @StéphaneChazelas It's not clear whether this is an issue or not. Re-reading the question, it's not clear what restrictions are at all.
    – Kusalananda
    Oct 4, 2021 at 13:38
  • @they Thanks for the suggestiosn. Actually, the above code I created only while writing the question and not verified. Fortunately, I havenot used in any of my scripts. I have been avoiding variables at all in the rm scripts.
    – GP92
    Oct 4, 2021 at 14:12
  • @they Besides, I think it will be highly useful if there is such util that can empty a given directory with only a single argument that is the directory itself and fails with multiple arguments (i..e, parsing error due to unquoted variables). I will start writing one in github.
    – GP92
    Oct 4, 2021 at 14:29
  • @GP92 At some point, you will have to start trusting the caller and say, "Hey, you messed up here, not me".
    – Kusalananda
    Oct 4, 2021 at 14:36
2

It's also a good idea to find alternative patterns where you don't end up needing rm -rf, or where it's somewhere safe.

One example might be making extensive use of mktemp with paths stored to local variables of functions so everything can just clean up after itself. If you aren't ingesting whole trees of unknown files into your process you might never need it.

If you are ingesting trees you can probably make sure there's a fixed prefix on your working path so you never wipe out / even if a variable is accidentally zero.

2

The simplest and most common protection against an empty variable with regard to rm -rf is ensuring the variable in not empty.

if [ -n "$directory" ]; then
   rm -rf "$directory"
fi

When you are appending to a base path this is an important check to make to ensure you don't end up passing only the base path, thus deleting far more than intended. Consider this example:

rm -rf "/home/user/$Dir"

If the variable $Dir is empty, the command will be rm -rf /home/user/.

If you know more about what you are deleting, you could also check to ensure the path is a file, -f, or a directory, -d.

See man test for most info on test expressions.

Quoting protects against spaces in the path name splitting it into 2 arguments.

5
  • What protection does -n offer, exactly, against the scenario of an unset or null value of directory? On my system, the command rm -rf by itself does nothing, and returns a successful result code. Sure, quoting is essential, but I don't see that -n is helpful. I suspect you meant to suggest the use of -d, such as: if [ -d "$directory" ]; then ...
    – Jim L.
    Oct 4, 2021 at 15:12
  • 2
    @JimL., if you have something like rm -rf ~/"$directory" it becomes much, much more important to check that the variable isn't empty. (with directory unset or null, the command would be more like rm -rf "", but that's also silently ignored.)
    – ilkkachu
    Oct 4, 2021 at 17:34
  • 1
    @ilkkachu Granted, but that's not the example in this answer. As posted, the test for [ -n "$directory" ] in this example is of no use whatsoever, that I can see.
    – Jim L.
    Oct 4, 2021 at 17:58
  • I was working in the context of the question and focusing only on a partial path and protecting against an unset variable. I can add more detail to the answer, but don't forget to check out the man page mentioned.
    – virullius
    Oct 5, 2021 at 14:53
  • test -n "$directory" will be false if $directory is unset or set to an empty string. That's literally what it does. It'll also accept a file instead of a directory, or a name of a file that doesn't exist. Either way it prevents an rm -rf $directory/ from resolving to an rm -rf / You probably want an else with an error message though.
    – davolfman
    Oct 6, 2021 at 23:41

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .