What is the safest way to empty a directory in *nix? - linux

I'm scared that one day, I'm going to put a space or miss out something in the command I currently use:
rm -rf ./*
Is there a safer way of emptying the current directory's contents?

The safest way is to sit on your hands before pressing Enter.
That aside, you could create an alias like this one (for Bash)
alias rm="pwd;read;rm"
That will show you your directory, wait for an enter press and then remove what you specified with the proper flags. You can cancel by pressing ^C instead of Enter.

Here is a safer way: use ls first to list the files that will be affected, then use command-line history or history substitution to change the ls to rm and execute the command again after you are convinced the correct files will be operated on.

If you want to be really safe, you could create a simple alias or shell script like:
mv $1 ~/.recycle/
This would just move your stuff to a .recycle folder (hello, Windows!).
Then set up a cron job to do rm -rf on stuff in that folder that is older than a week.

I think this is a reasonable way:
find . -maxdepth 1 \! -name . -print0 | xargs -0 rm -rf
and it will also take care of hidden files and directories. The slash isn't required after the dot and this then will also eliminate the possible accident of typing . /.
Now if you are worried what it will delete, just change it into
find . -maxdepth 1 \! -name . -print | less
And look at the list. Now you can put it into a function:
function enum_files { find . -maxdepth 1 \! -name . "$#"; }
And now your remove is safe:
enum_files | less # view the files
enum_files -print0 | xargs -0 rm -rf # remove the files
If you are not in the habit of having embedded new-lines in filenames, you can omit the -print0 and -0 parameters. But i would use them, just in case :)

Go one level up and type in the directory name
rm -rf <dir>/*

I use one of:
rm -fr .
cd ..; rm -fr name-of-subdirectory
I'm seldom sufficiently attached to a directory that I want to get rid of the contents but must keep the directory itself.

When using rm -rf I almost always use the fully qualified path.

Use the trash command. In Debian/Ubuntu/etc., it can be installed from the package trash-cli. It works on both files and directories (since it's really moving the file, rather than immediately deleting it).
trash implements the freedesktop.org trash specification, compatible with the GNOME and KDE trash.
Files can be undeleted using restore-trash from the same package, or through the usual GUI.

You could always turn on -i which would prompt you on every file, but that would be really time consuming for large directories.
I always do a pwd first.
I'll even go as far as to create an alias so that it forces the prompt for my users. Red Hat does that by default, I think.

You could drop the `f' switch and it should prompt you for each file to make sure you really want to remove it.

If what you want to do is to blow away an entire directory there is always some level of danger associated with that operation. If you really want to be sure that you are doing the right thing you could always do a move operation to some place like /tmp, wait for some amount of time to make sure that everything is okay with the "deletion" in place. Then go into the /tmp directory and ONLY use relative paths for a forced and recursive remove operation. Additional, in the move do a rename to "delete-directoryname" to make it easier not to make a mistake.
For example I want to delete /opt/folder so I do:
mv /opt/folder /tmp/delete-folder
.... wait to be sure everything is okay - maybe a minute, maybe a week ....
cd /tmp
pwd
rm -rf delete-folder/
The most important tip for doing an rm -rf is to always use relative paths. This keeps you from ever having typed a / before having completed your typing.

There's a reason I have [tcsh]:
alias clean '\rm -i -- "#"* *~'
alias rmo 'rm -- *.o'
They were created the first time I accidentally put a space between the * and the .o. Suffice to say, what happened wasn't what I expected to happen...
But things could have been worse. Back in the early '90s, a friend of mine had a ~/etc directory. He wanted to delete it. Unfortunately he typed rm -rf /etc. Unfortunately, he was logged in as root. He had a bad day!
To be evil: touch -- '-rf *'
To be safe, use '--' and -i. Or get it right once and create an alias!

Here are the alias I am using on macOS. It would ask for every rm command before executing.
# ~/.profile
function saferm() {
echo rm "$#"
echo ""
read -p "* execute rm (y/n)? : " yesorno
if [ $yesorno == "y" ]; then
/bin/rm "$#"
fi
}
alias srm=saferm
alias rm=srm

Related

ZSH+GREP+REGEX. Why this snippet act as rm -r /

This is a little anecdote from earlier on why not running root is vital.
I was sorting my home directory and deleted a few compressed files I had, I wrote
ls . | grep -P 'zip|tar|7z' | xargs rm and thought, hey I could also write this as rm -r $(ls . | grep -P '...') I suppose.
The second part I didn't mean to use it since there was nothing to delete, it was morelike a mental exercise, I wrote it next to the last command with a 'divider' to visually compare them.
ls . | grep -P 'zip|tar|7z' | xargs rm **//** rm -r $(ls . | grep -P '...')
Being **//** the "divider" and ... the mental "substitute" for 'zip|tar..'
I thought this wouldn't run but to my surprise, it acted as rm -r /and tried to delete everything, luckily permissions saved me and nothing was deleted.
But I'm curious why it'd work that way,
my guess is that rm **//** somehow translated to rm / but I'm not sure.
In the zsh shell, **//** would expand to all names under / as well to all names below the current directory (recursively).
From an empty directory on my system:
$ echo **//**
/altroot /bin /boot /bsd /bsd.booted /bsd.rd /bsd.sp /dev /etc /extra /home /mnt /root /sbin /sys /tmp /tmp_mnt /usr /var /vol
Why? Well, **/ matches all directories recursively under the current directory. More importantly, it matches the current directory, but since the current directory's name is not available inside the current directory, there's no entry returned for that.
However, when you add a / to that to create **//, then you get a lone / back for the current directory. Again in an empty directory:
$ echo **//
/
Then, if you add a further ** to make **//**, you pick up all names from the root directory, together with all names from the current directory and below (directory names from the current directory and below would occur twice in the list).
Your xargs is calling
rm **//** rm -r $(ls . | grep -P '...')
If you're using GNU rm, it will helpfully rearrange the command line so that it is interpreted the same as
rm -r **//** rm $(ls . | grep -P '...')
What this does should now be clear.
If you want to delete all regular files in the current directory that have filename suffixes .zip, .tar or .7z, use
rm ./*.(zip|tar|7z)(.)
in the zsh shell. If want to do that recursively down into subdirectories, use
rm ./**/*.(zip|tar|7z)(.)
The glob qualifier (.) makes the globbing pattern only match regular files. You could even restrict it to files above a certain size, say 10MB, with ./**/*.(zip|tar|7z)(.Lm+10).
One difference is that the ls ... | xargs .... solution also works if there are really a lot of files involved, while your rm $( .... ) might produce a argument list too long error. But if this is not an issue in your case, an even simpler attempt would be (assuming here Zsh; I don't understand why you tagged this bash, since you explicitly refer to Zsh only in your question)
rm *(zip|tar|7z)*(N)
which would express your original statement; I believe however that you really meant
rm -- *.(zip|tar|7z)(N)
because the solution you posted would also remove a file tarpit.txt, for instance. The (N) flag is a frail attempt to treat the case, that you don't have any file matching the pattern. Without the (N), you would get an error message from Zsh, and rm would receive the unexpanded file pattern, and, since it is unlikely that a file of this name exists, would output a second error message. By using (N), Zsh would simply pass nothing in this case (without complaining), and in fact rm would be invoked without arguments. Of course you would then get a rm: missing operand on stderr, and if you don't like this, you can filter this message.
UPDATES:
As Kusalananda has pointed out in his/her comment, omitting the (N) would, by default, make zsh only print an error message, if no files match the pattern, but not cause rm to be invoked.
Also added the -- flag to rm to allow removal of, i.e., a file called -rf.tar.

Removing files called --exclude=*.xdr

Somehow I must have mistyped a command, because now I have files named --exclude=.xdr and --exclude=.h5 in one of my directories. I want to delete them. Only problem is whenever I do something like:
rm --exclude=*.xdr
it thinks I'm passing an argument to the rm command. I've tried encasing in single and double quotes but it still didn't work. How can I delete these files?
Cheers
Flag interpretation is done based purely on text. Any string that doesn't start with a - is not a flag. The path to a file in the local directory can start with ./ (the . means "current directory").
I'd also recommend reading the man page for rm, as that explicitly lists two different ways of doing exactly this.
rm -- --blah
rm ./--blah
rm -- "--exclude=.xdr"
Use this command for delete that file
What about using find:
find . -type f -name "--exclude*" -exec rm {} \; -print

Unix command deleted every directory even though not specified

I am very new to the unix. I ran the following command.
ls -l | xargs rm -rf bark.*
and above command removed every directory in the folder.
Can any one explained me why ?
The -r argument means "delete recursively" (ie descend into subdirectories). The -f command means "force" (in other words, don't ask for confirmation). -rf means "descend recursively into subdirectories without asking for confirmation"
ls -l lists all files in the directory. xargs takes the input from ls -l and appends it to the command you pass to xargs
The final command that got executed looked like this:
rm -rf bark.* <output of ls -l>
This essentially removed bark.* and all files in the current directory. Moral of the story: be very careful with rm -rf. (You can use rm -ri to ask before deleting files instead)
rm(1) deleted every file and directory in the current working directory because you asked it to.
To see roughly what happened, run this:
cd /etc ; ls -l | xargs echo
Pay careful attention to the output.
I strongly recommend using echo in place of rm -rf when constructing command lines. Only if the output looks fine should you then re-run the command with rm -rf. When in doubt, maybe just use rm -r so that you do not accidentally blow away too much. rm -ir if you are very skeptical of your command line. (I have been using Linux since 1994 and I still use this echo trick when constructing slightly complicated command lines to selectively delete a pile of files.)
Incidentally, I would avoid parsing ls(1) output in any fashion -- filenames can contain any character except ASCII NUL and / chars -- including newlines, tabs, and output that looks like ls -l output. Trying to parse this with tools such as xargs(1) can be dangerous.
Instead, use find(1) for these sorts of things. To delete all files in all directories named bark.*, I'd run a command like this:
find . -type d -name 'bark.*' -print0 | xargs -0 rm -r
Again, I'd use echo in place of rm -r for the first execution -- and if it looked fine, then I'd re-run with rm -r.
The ls -l command gave a list of all the subdirectories in your current present-working-directory (PWD).
The rm command can delete multiple files/directories if you pass them to it as a list.
eg: rm test1.txt test2.txt myApp will delete all three of the files with names:
test1.txt
test2.txt
myApp
Also, the flags for the rm command you used are common in many a folly.
rm -f - Force deletion of files without asking or confirming
rm -r - Recurse into all subdirectories and delete all their contents and subdirectories
So, let's say you are in /home/user, and the directory structure looks like so:
/home/user
|->dir1
|->dir2
`->file1.txt
the ls -l command will provide the list containing "dir1 dir2 file1.txt", and the result of the command ls -l | xargs rm -rf will look like this:
rm -rf dir1 dir2 file1.txt
If we expand your original question with the example above, the final command that gets passed to the system becomes:
rm -rf di1 dir2 file1.txt bark.*
So, everything in the current directory gets wiped out, so the bark.* is redundant (you effectively told the machine to destroy everything in the current directory anyway).
I think what you meant to do was delete all files in the current directory and all subdirectories (recurse) that start with bark. To do that, you just have to do:
find -iname bark.* | xargs rm
The command above means "find all files in this directory and subdirectories, ignoring UPPERCASE/lowercase/mIxEdCaSe, that start with the characters "bark.", and delete them". This could still be a bad command if you have a typo, so to be sure, you should always test before you do a batch-deletion like this.
In the future, first do the following to get a list of all the files you will be deleting first to confirm they are the ones you want deleted.
find -iname bark.* | xargs echo
Then if you are sure, delete them via
find -iname bark.* | xargs rm
Hope this helps.
As a humorous note, one of the most famous instances of "rm -rf" can be found here:
https://github.com/MrMEEE/bumblebee-Old-and-abbandoned/commit/a047be85247755cdbe0acce6f1dafc8beb84f2ac
An automated script runs something like rm -rf /usr/local/........., but due to accidentally inserting a space, the command became rm -rf /usr /local/......, so this effectively means "delete all root folders that start with usr or local", effectively destroying the system of anyone who uses it. I feel bad for that developer.
You can avoid these kinds of bugs by quoting your strings, ie:
rm -rf "/usr/ local/...." would have provided an error message and avoided this bug, because the quotes mean that everything between them is the full path, NOT a list of separate paths/files (ie: you are telling rm that the file/folder has a SPACE character in its name).

Find and remove over SSH?

My web server got hacked (Despite the security team telling me nothing was compromised) and an uncountable number of files have an extra line of PHP code generating a link to some Vietnamese website.
Given there are tens of thousands of files across my server, is there a way I can go in with SSH and remove that line of code from every file it's found in?
Please be specific in your answer, I have only used SSH a few times for some very basic tasks and don't want to end up deleting a bunch of my files!
Yes, a few lines of shell script would do it. I hesitate to give it to you, though, as if something goes wrong I'll get blamed for messing up your web server. That said, the solution could be as simple as this:
for i in `find /where/ever -name '*.php'`; do
mv $i $i.bak
grep -v "http://vietnamese.web.site" $i.bak >> $i
done
This finds all the *php files under /where/ever, and removes any lines that have http://vietnamese.web.site in them. It makes a *.bak copy of every file. After you run this and all seems good, you could delete the backups with
find . -name '*.php.bak' -exec rm \{\} \;
Your next task would be to find a new provider, as not only did they get hacked, but they apparently don't keep backups. Good luck.
First create a regex, that matches the bad code (and only the bad code), then run
find /path/to/webroot -name \*.php -exec echo sed -i -e 's/your-regex-here//' {} \;
If everything looks right, remove the echo
I do it following way. E.g. to delete files matching particular name or extension.
rm -rf * cron.php. *
rm -rf * match_string *
where match_string will be any string. Make sure there will be no space between * and string name.
rm -f cron.php.*
Delete all file in this folder called cron.php.[whereveryouwant]

How can I batch rename files to lowercase in Subversion?

We are running subversion on a Linux server. Someone in our organization overwrote about 3k files with mixed case when they need to all be lowercase.
This works in the CLI
rename 'y/A-Z/a-z/' *
But obviously will screw up subversion.
svn rename 'y/A-Z/a-z/' *
Doesn't work because subversion deals with renames differently I guess. So How can I do this as a batch job? I suck with CLI, so please explain it like I'm your parents. hand renaming all 3k files is not a task I wish to undertake.
Thanks
Create a little script svn-lowername.sh:
#!/bin/bash
# svn rename each supplied file with its lowercase
for src; do
dst="$(dirname "$src")/$(basename "$src" | tr '[A-Z]' '[a-z]')"
[ "$src" = "$dst" ] || svn rename "$src" "$dst"
done
Make sure to chmod +x svn-lowername.sh. Then execute:
How it works:
Loop over each supplied filename.
Basename part of filename is lowercased, directory part is left alone, using
tr to do character by character translations. (If you need to support
non-ASCII letters, you'll need to make a more elaborate mapping.)
If source and destination filenames are the same we're okay, otherwise, svn rename. (You could use an if statement instead, but for one-liner
conditionals using || is pretty idiomatic.)
You can use this for all files in a directory:
./svn-lowername.sh *
...or if you want to do it recursively:
find -name .svn -prune -o -type f -print0 | xargs -0 ./svn-lowername.sh
If you think this problem might happen again, stash svn-lowername.sh in your
path somewhere (~/bin/ might be a good spot) and you can then lose the ./
prefix when running it.
This is a bit hacky, but should work on any number of nested directories:
find -mindepth 1 -name .svn -prune -o -print | (
while read file; do
svn mv "$file" "`perl -e 'print lc(shift)' "$file"`"
done
)
Also, you could just revert their commit. Administering the clue-by-four may be prudent.
Renames in Subversion require two steps, an svn copy followed by an svn delete if you want to retain history. If you use svn move you will loose the history of the file.
While this answer doesn't help with your question, it will help keep this from happening again. Implement the case-insenstive.py script in your pre-commit hook on your Subversion repository. The next time someone tries to add a file with a different case, the pre-commit hook won't let them.

Resources