Bash: What's the difference between "rm -d" and "rm -R"? - bash

Questions
What is the difference between the rm -d and rm -R commands in Bash?
Which one should I use?
Details
According to the man page for the rm command:
rm -d attempts to remove directories as well as other types of
files.
rm -R attempts to remove the file hierarchy rooted in each file
argument. The -R option implies the -d option.
Now, I am aware of that last statement (-R implies -d), which may seem to answer my question. However, I still wonder why both command flags exist in the first place, if they are supposedly identical in what they do.
Furthermore, because I am still in the process of learning Bash, I think it's good to know which option is the preferred choice among Bash programmers (conventionally), and why.

Ordinarily, rm will not remove a directory, even if it is empty. rm -d just makes rm act like rmdir. It still refuses to remove the directory if it isn't empty, but will do so if it is empty.
rm -R is the full recursive delete, removing the directory and all its contents.
I've never used -d, as I didn't know it existed and always just use rmdir. I'd use rmdir/rm -d if you only want to remove the directory if it is, in fact, empty. Save rm -R for when you are fully aware that you are trying to remove a directory and all its contents.

The -d option is particular to the BSD implementation of rm, which is the one you are likely finding on your Mac. It is not present in the GNU implementation you will find on Linux systems.
If you are looking for the preferred choice, it would be to use -r (lowercase) to remove whole trees and rmdir for removing single directories, which you will find to be code that is more portable.

rm will delete files, but i had issues trying to remove directory, so it need to be flagged with some option,
rm -f will force remove file without asking for confirmation
rm -R will remove directory but in my case was asking for confirmation, but because i have so many files, i kept on typing y and y which was taken for eternity
rm -Rf was my final solution, it forced remove the directory without asking for confirmation

Just to be clear, you should never use rm -d. Assuming it doesn't just fail with an "Operation not permitted" error message, it will remove the directory without removing the contents. On an empty directory it's the same as rmdir. On a non-empty directory it creates an inconsistency in the filesystem requiring repair by fsck or by some very clever manual hacking.
It's a stupid option that should never have existed. The BSD people were on some bad drugs when they added it. rm -r had been in UNIX since at least 1973, and rmdir since 1971.

Related

Operating on multiple specific folders at once with cp and rm commands

I'm new to linux (using bash) and I wanted to ask about something that I do often while I work, I'll give two examples.
Deleting multiple specific folders inside a certain directory.
Copying multiple specific folders into a ceratin directory.
I succesfully done this with files, using find with some regex and then using -exec and -delete. But for folders I found it more problematic, because I had problem pipelining the list of folders I got to the cp/rm command succescfully, each time getting the "No such file or directory error".
Looking online I found the following command (in my case for copying all folders starting with a Z):
cp -r $(ls -A | grep "Z*") destination
But when I execute it it says nothing and the prompt won't show up again until I hit Ctrl+C and nothing is copied.
How can I achieve what I'm looking for? For both cp and rm.
Thanks in advance!
First of all, you are trying to grep "Z*" but it means you are looking for Z, ZZ, ZZZZ, ZZZZZ ?
also try to execute ls -A - you will get multiple columns. I think need at least ls -1A to print result one per line.
So for your command try something like:
cp -r $(ls -1A|grep "^p") destination
or
cp -r $(ls -1A|grep "^p") -t destination
But all the above is just to correct syntax of your example.
It is much better to use find. Just in case try to put target directory in quotas like:
find <PATH_FROM> -type d -exec cp -r \"{}\" -t target \;

Different ways to make "make clean" idempotent without suppressing errors or using rm -rf?

I have a makefile project which generates 2 folders a build one and a deps one. I would like to be able to run make clean as many times as I want and the result would be to only delete the folders and the files in them the first time (idempotent make clean essentially).
Any subsequent time it wouldn't throw any errors, it would just do nothing.
Additionally because I accidentally deleted my home folder once by using rm -rf in a makefile, I would like to avoid using that as well.
I have tried various combinations of using rm -r, rm -f, rmdirs and/or adding a prefix (-) which will only suppress the errors.
Additionally I know I can solve this by using something like the following
if [-d "./build"]; then
rm -r ./build
fi
Do you guys have any other solutions?
Not a fan of clean targets, just tell users to delete the build/ folder (I like your hygiene!).
rm -f is the correct solution. -rf with a folder is fine. If you are feeling paranoid, just protect yourself.
.PHONY: clean
clean:
rm -rf $(or ${build-dir},$(error $$build-dir does not exist))
Here, make will expand the recipe before giving it to the shell. If the expansion of $build-dir is empty, make will expand the $(error …) and will stop, issue the error message and an error, and not even get as far as running the rm.
If you really don't want to use -f with rm, then
-rm -r ${build-dir}
will cause make to ignore any error, but is a bit noisy.
rm -r ${build-dir} || :
will attempt to run the rm. If that succeeds then your task is done and make is happy. If the rm fails, the shell will run its built-in : command which simply returns success, and make is again happy.
find paths/ -delete may be useful, although as written it will fail if paths don't exist. There are many options and it depends on exactly what you want to do.
But rm -f is by far the standard for simple removal.
Remember that you can use || [ $$? -eq 1 ] or similar to suppress specific errors - note the doubled $ for make's own quoting.
Another way of achieving idempotency is to rename things to temporary names and later either rename them into place or delete them, but I'm not sure if that's relevant here.
"because I accidentally deleted my home folder once by
using rm -rf, I'd like to avoid it"
Removing rm -rf doesn't actually make much sense, since it does exactly what you want. It's only your fear that you want to find different solution, but that would be dangerous as well :-)
I'd suggest to use rm -rf but very carefully. I was using it for years and nothing bad happened. My example of use in the Makefile:
lang_dir = lang
domain = messages
clean:
rm -rf $(lang_dir) $(domain).pot tmp_err
How could you compromise that? There's no way. Be careful but do not irrationally fear rm -rf just because it is THE INFAMOUS rm -rf :-)
If you are super unsure, you might require a manual confirmation for recursive removal by adding option -I:
rm -rfI target1 target2 ...
From the rm(1) manual page:
-I prompt once before removing more than three files, or when removing recursively.
Less intrusive than -i, while still giving protection against most mistakes
Whatever solution you use, rm -rf, if [ -d ... ]; then rm -r ...; fi, find ... -delete etc. - you'll always have the same fundamental problem: if the thing you're trying to delete contains a variable dereference, and the variable is accidentally empty, you will delete the wrong directory.
So there's rm -I (which might not work on a build server), and also rm --preserve-root, and also safe-rm.
If you're paranoid, you could have a directory called e.g. cleanups which contains links to the directories you want to clean, and have your makefile have:
clean:
rm -rf cleanups/*/*
rmdir $(readlink cleanups/*)
In the rm call above, the first star finds the links, second deletes everything in these directories, NOT including files starting with . (i.e. hidden files).
The rmdir line removes these empty directories, if they are indeed empty, assuming there are no spaces in the link names.

Removing existing file in a directory

I'm trying to check if a directory if existing file, if so then I would like to remove the file before moving new file in that directory.
if [[${abc_directory}/filename*.dmp]] ; then
rm -rf ${abc_directory}/filename*.dmp
I think I am missing some logic in the if statement.
Simply do:
rm -f "${abc_directory}"/filename*.dmp
The option -f (--force) tells rm to ignore non existing files.
I have not included -r, because following your description, you will remove single files and not directory trees.
I have escaped the ${abc_directory} part to avoid issues with spaces. It assumes, that abc_directory doesn't contain any wanted wildcards/placeholders.
You're not looking for a specific file in your example, you're using a wild card. Why not just do a rm -rf ${abc_directory}/filename*.dmp before moving in the new file and be done with it?
Here's how to check for existence of a single file.
if [ -e ${abc_directory}/filename.dmp ];then
rm -f ${abc_directory}/filename.dmp
fi

Explaining the rm ./-rf "trick"

This question states:
It is amazing how many users don't know about the rm ./-rf or rm -- -rf tricks.
I am afraid to try these, but curious as to what they do. They are also very difficult to search...
Can someone enlighten me?
rm ./-rf and/or rm -- -rf would attempt to remove a file named, specifically, -rf
The only trick here is that you normally can't delete a file that starts with a "-" because the command will assume it's a command argument. By preceding the file with a full path, or using the -- option (which means, end all options) the command will no longer assume it's an argument.
It should be noted that the -- version of this trick may or may not work with all shell commands either, so it's best to use the first version.
If you have a file named -rf in your directory, it is difficult to remove that file if you don't know the trick. That's because:
rm -rf
supplies two command line options (-r and -f) as a single argument, and tells rm to recursively and forcibly remove things.
If you write:
rm ./-rf
the argument does not start with a dash any more, so it is simply a file name. Similarly, by common (but not universal) convention, -- marks the end of the option arguments and anything afterwards is no longer an option (which usually means it is a file name). So:
rm -- -rf
removes the file because rm knows that the arguments that follow the -- are file names, not options for it to process.
The file -rf is even more dangerous if you use wildcards:
rm *rf*
Suddenly, this will remove directories as well as files (but won't harm the file -rf).
Not a complete answer, as I think the other answers give good explanations.
When I'm unsure what a given rm invocation is going to delete, I try to remember to simply ls the file list first to make sure it is actually what I want to delete:
$ ls -rf
-rf .. .
$
OK, clearly thats not right, lets try again:
$ ls ./-rf
./-rf
$
Thats better. Lets do a history replacement of ls with rm -v (-v just for extra paranoia/checking) and do the actual delete:
$ rm -v !!:*
rm -v ./-rf
removed `./-rf'
$
This also works nicely with wildcards, brace expansions, etc, when you're not sure what the expansion will be exactly.
Also if you're wondering how files like -rf get created in the first place, its astonishingly easy if you mess up a redirection a bit:
$ ls
$ echo > -rf
$ ls
-rf
$

How to remove files starting with double hyphen?

I have some files on my Unix machine that start with
--
e.g. --testings.html
If I try to remove it I get the following error:
cb0$ rm --testings.html
rm: illegal option -- -
usage: rm [-f | -i] [-dPRrvW] file ...
unlink file
I tried
rm "--testings.html" || rm '--testings.html'
but nothing works.
How can I remove such files on terminal?
rm -- --testings.html
The -- option tells rm to treat all further arguments as file names, not as options, even if they start with -.
This isn't particular to the rm command. The getopt function implements it, and many (all?) UNIX-style commands treat it the same way: -- terminates option processing, and anything after it is a regular argument.
http://www.gnu.org/software/hello/manual/libc/Using-Getopt.html#Using-Getopt
rm -- --somefile
While that works, it's a solution that relies on rm using getopts for parsing its options. There are applications out there that do their own parsing and will puke on that too (because they might not necessarily implement the "-- means end of options" logic).
Because of that, the solution you should drive through your skull is this one:
rm ./--somefile
It will always work, because this way your arguments never begin with a -.
Moreover, if you're trying to make really decent shell scripts; you should technically be putting ./ in front of all your filename parameter expansions to prevent your scripts from breaking due to funky filename input (or to prevent them being abused/exploited to do things they're not supposed to do: for instance, rm will delete files but skip over directories; while rm -rf * will delete everything. Passing a filename of "-rf" to a script or somebody touch ~victim/-rf'ing could in this way be used to change its behaviour with really bad consequences).
Either rm -- --testings.html or rm ./--testings.html.
rm -- --testings.html
Yet another way to do it is to use find ... -name "--*" -delete
touch -- --file
find -x . -mindepth 1 -maxdepth 1 -name "--*" -delete
For a more generalised approach for deleting files with impossible characters in the filename, one option is to use the inode of the file.
It can be obtained via ls -i.
e.g.
$ ls -lai | grep -i test
452998712 -rw-r--r-- 1 dim dim 6 2009-05-22 21:50 --testings.html
And to erase it, with the help of find:
$ find ./ -inum 452998712 -exec rm \{\} \;
This process can be beneficial when dealing with lots of files with filename peculiarities, as it can be easily scripted.
rm ./--testings.html
or
rm -- --testings.html

Resources