Why | doesn't work with find? - linux

Why does the following command aiming to remove recursively all .svn folders
find . -name ".svn" | rm -rfv
doesn't work ?
I know the find command provides the -exec option to solve this issue but I just want to understand what is happening there.

In your example, the results from find are passed to rm's STDIN. rm doesn't expect its arguments in STDIN, though.
Here is an example how input redirecting works.

rm does not read file names from standard input, so any data piped to it is ignored.
The only thing it uses standard input for is checking whether it's a terminal, so it can determine whether to prompt.

It doesn't work because rm does not accept a list of file names on its standard input stream.
Just for reference, the safest way to handle this in the case of directories that might contain spaces is:
find . -name .svn -exec rm -frv {} \;
Or, if you are shooting for speed:
find . -name .svn -print0 | xargs -0 rm -frv

find do works with | ( for example find ~ -name .svn | grep "a") but the problem is with rm

This question is similar to this other answered question. Hope this helps.
How do I include a pipe | in my linux find -exec command?

Related

How do I write a command to search and remove files, even in cases where the files can't be found?

I’m using Amazon Linux with the bash shell. I want to find and remove some PDF files in a single line, so I tried
find /home/jboss/.jenkins/jobs/myco/workspace/ebook/ -name '*.pdf' | xargs rm
This works fine if there are PDF files. But if there are none, I get the error
rm: missing operand
Is there any way to write the above statement in a single line so that it will not fail, even if there are no files to remove?
This can easily be achieved using the -r flag to xargs.
I also recommend using "special character tolerant" version:
find /home/jboss/.jenkins/jobs/myco/workspace/ebook/ -name '*.pdf' -print0 | xargs -0 -r rm
Have you tried doing it all within the find command?
find /home/jboss/.jenkins/jobs/myco/workspace/ebook/ -name '*.pdf' -exec rm -f {} \;
I've always used the construct above though I believe you can also use the switch -delete which may be a bit more efficient. If you do use it remember to put -delete at the end as find is evaluated left to right as an expression.
You don't even need find, you can simply use rm, it supports basic pattern matching. Just do the following:
rm -f path/*.pdf

Piping find results into grep for fast directory exclusion

I am successfully using find to create a list of all files in the current subdirectory, excluding those in the subdirectory "cache." Here's my first bit of code:
find . -wholename './cach*' -prune -o -print
I now wish to pipe this into a grep command. It seems like that should be simple:
find . -wholename './cach*' -prune -o -print | xargs grep -r -R -i "samson"
... but this is returning results that are mostly from the cache directory. I've tried removing the xargs reference, but that does what you'd expect, running the grep on text of the file names, rather than on the files themselves. My goal is to find "samson" in any files that aren't cached content.
I'll probably get around this issue by just using doubled greps in this instance, but I'm very curious about why this one-liner behaves this way. I'd love to hear thoughts on a way to modify it while still using these two commands (as there are speed advantages to doing it this way).
(This is in CentOS 5, btw.)
The wholename match may be the reason why it's still including "cache" files. If you're executing the find command in the directory that contains the "cache" folder, it should work. If not, try changing it to -name '*cache*' instead.
Also, you do not need the -r or -R for your grep, that tells it to recurse through directories - but you're testing individual files.
You can update your command using the piped version, or a single-command:
find . -name '*cache*' -prune -o -print0 | xargs -0 grep -il "samson"
or
find . -name '*cache*' -prune -o -exec grep -iq "samson" {} \; -print
Note, the -l in the first command tells grep to "list the file" and not the line(s) that match. The -q in the second does the same; it tells grep to respond quietly so find will then just print the filename.
You've told grep itself to recurse (twice! -r and -R are synonyms). Since one of the arguments you're passing is . (the top directory), grep is searching in every file (some of them twice, or even more if they're in subdirectories).
If you're going to use find and grep, do this:
find . -path './cach*' -prune -o -print0 | xargs -0 grep -i "samson"
Using -print0 and -0 makes your script work even with file names that contain spaces or punctuation characters.
However, you probably don't need to bother with find here, since GNU grep is capable of excluding directories:
grep -R --exclude-dir='cach*' -i "samson" .
(This also excludes ./deeply/nested/directory/cache. If you only want to exclude cache directories at the toplevel, use find as you did.)
Use the -exec option on find instead of piping them to another command. From there you can use grep "samson" {} \; to look for samson in each file listed.
For example:
find . -wholename './cach*' -prune -o -exec grep "samson" "{}" +

Loop over file names from `find`?

If I run this command:
sudo find . -name *.mp3
then I can get a listing of lots of mp3 files.
Now I want to do something with each mp3 file in a loop. For example, I could create a while loop, and inside assign the first file name to the variable file. Then I could do something with that file. Next I could assign the second file name to the variable file and do with that, etc.
How can I realize this using a linux shell command? Any help is appreciated, thanks!
For this, use the read builtin:
sudo find . -name *.mp3 |
while read filename
do
echo "$filename" # ... or any other command using $filename
done
Provided that your filenames don't use the newline (\n) character, this should work fine.
My favourites are
find . -name '*.mp3' -exec cmd {} \;
or
find . -name '*.mp3' -print0 | xargs -0 cmd
While Loop
As others have pointed out, you can frequently use a while read loop to read filenames line by line, it has the drawback of not allowing line-ends in filenames (who uses that?).
xargs vs. -exec cmd {} +
Summarizing the comments saying that -exec...+ is better, I prefer xargs because it is more versatile:
works with other commands than just find
allows 'batching' (grouping) in command lines, say xargs -n 10 (ten at a time)
allows parallellizing, say xargs -P4 (max 4 concurrent processes running at a time)
does privilige separation (such as in the OP's case, where he uses sudo find: using -exec would run all commands as the root user, whereas with xargs that isn't necessary:
sudo find -name '*.mp3' -print0 | sudo xargs -0 require_root.sh
sudo find -name '*.mp3' -print0 | xargs -0 nonroot.sh
in general, pipes are just more versatile (logging, sorting, remoting, caching, checking, parallelizing etc, you can do that)
How about using the -exec option to find?
find . -name '*.mp3' -exec mpg123 '{}' \;
That will call the command mpg123 for every file found, i.e. it will play all the files, in the order they are found.
for file in $(sudo find . -name *.mp3);
do
# do something with file
done

find and remove files with space using find command on Linux

I'm trying to remove all thumbs.db files in a Windows partition using find command in Ubuntu:
find . -iname "*.db"|while read junk;do rm -rfv $junk;done
But it's not working for me and nothing happens! I think I found the problem, the white spaces in directory names!
I did this trick to remove my junk files before on previous version of Ubuntu but now on latest version of Ubuntu I can't.
Is there any bug in my command?
I'd do it this way:
find . -iname 'thumbs.db' -exec rm -rfv {} +
This way, it still works even if your directories contain whitespace in their names.
just to throw this out there
find . -name "*.pyc" -delete
I'm not sure why you're using while.
find . -iname 'thumbs.db' -exec rm -rfv {} \;
...should suffice (and only delete the files you want to, not any BDB files that may be laying around).
The code looks good and works on arch and debian. Maybe there are no files matching "*.db"?
As a sidenote: I might not be a good idea to delete all files with the suffix ".db", because you can accidently delete other files than "Thumbs.db"
First check if the first part of your command, that is:
find . -iname "*.db"
is returning anything.
If it does then you can use xargs as follows to accomplish your task:
find . -iname "*.db" | xargs rm -rfv
UPDATE: From comments, this is unsafe, specially if there are spaces in directory/file names. You will need to use -print0 / xargs -0 to make it safe.

How does one find and copy files of the same extension, in different directories, to a single directory in linux?

So, How Do I find and copy all files,
*.a
that are in,
~/DIR{1,2,3,...}
to
~/tmp/foo?
Assumed you meant recursively copy everything of type .a from some source location.
Haven't verified yet, but this should do that.
find <root-of-search> -type f -name '*.a' -exec cp {} /tmp/foo \;
replace with the top of wherever you want to search from. You might have to throw quotes around *.a, and you might have to replace escape the ending semicolon by putting it in single quotes rather than back-slashing it.
In a bash shell:
cp ~/DIR*/*.a ~/tmp/foo
find ~/DIR{1,2,...} -name *.a print0 | xargs -i -0 cp '{}' ~/tmp/foo

Resources