Explaining the rm ./-rf "trick" - bash

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
$

Related

remove with bash lots of files with spaces in their names

Okey, my question seems to be related to this one:
Trouble in script with spaces in filename
but the answer doesn't seem to tackle the problem when you have to deal with a bunch of those files.
Let us say that I get some filenames of files that I want to erase with find, and that I put them in a text file. Then I want to delete them, with rm, say. rm seems not to be able to interpret the spaces of the filenames, even after I put the names inside quotes manually!
What would you suggests?
It depends on how you're actually processing the file list (which you haven't yet shown). Assuming you have them one per line in the file:
:: cat list
thishasnospaces
but this does
then even something like this won't work if they have spaces:
:: for fspec in $(cat list) ; do echo "rm -f \"${fspec}\"" ; done
rm -rf "thishasnospaces"
rm -rf "but"
rm -rf "this"
rm -rf "does"
That's because this treats all white space as identical. However, you can do it with a while loop:
:: cat list | while read ; do echo "rm -f \"$REPLY\""; done
rm -f "thishasnospaces"
rm -f "but this does"
You'll see that preserves the one per line aspect. Just remove the echo when you're happy it will work.
:: cat list | while read ; do rm -f "$REPLY"; done
But just keep in mind this may all be unnecessary. The find command already has the capability to delete files that it finds, bu use of the -delete option. If you can use that, there'll be far less messing about with spaces in the filenames.
Why do you need a text file?
Try something like:
touch "a b"
touch "c d"
find . -name "* *" -exec rm "{}" \;
Good luck!

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.

How to delete a file named "~" in Mac?

I run the following command on my Macbook:
mkdir ~/tmp/~
Now, I want to delete this ~/tmp/~
.
How to do it? It is not a link actually, if I run rm -rf ~/tmp/~, the home files are all dropped.
So interesting. This task can be done in this way.
# This form is safe and functional.
rm -rf ~/tmp/~
But if you try to do this, your home data is going to be lost:
# THIS FORM IS DANGEROUS; DO NOT USE IT
cd ~/tmp
rm -rf ~
I agree with Ivan X in his comment, the ~ in this context does not cause any particular problems and rmdir ~/tmp~ removes the directory without problem (see #1 below).
However, what you describe is a classic Unix/BSD problem. In order to remove a directory that contains special characters you have to make sure your shell ignores interpolation of them. There are two methods to achieve this.
You can use a full path, e.g. rmdir ~/tmp/~ or...
In the case of a file or directory starting with -, you can use -- to tell the shell that there are no options following, ala rmdir -- -foo-
So interesting. This task can be done in this way.
# This form is safe and functional.
rm -rf ~/tmp/~
But if you try to do this, your home data is going to be lost:
# THIS FORM IS DANGEROUS; DO NOT USE IT
cd ~/tmp
rm -rf ~
Quite so, and quite simply because (see Tilde Expansion):
If a word begins with an unquoted tilde character
(‘~’), all of the characters up to the first
unquoted slash (or all characters, if there is no unquoted slash) are
considered a tilde-prefix. If none of the characters in the
tilde-prefix are quoted, the characters in the tilde-prefix following
the tilde are treated as a possible login name. If this
login name is the null string, the tilde is replaced with the value of
the HOME shell variable.
Thus, rm -rf ~ is expanded as rm -rf $HOME.
Deletion of any file with a funny name is better done with a File browser. It is simply easier and safer.
If you don't want to use Finder, use any other text based file browser, e.g. emacs dired mode.
[...]
In case you insist in doing this using a shell...
ls -i # will show you the inode number of the files you have
Once you have the right inode, say 123456789, you can use find to delete it.
find . -maxdepth 1 -type f -inum 123456789 -delete

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

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.

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