sorry if this is a noobie question but I can't find a good answer.
To find then remove something I can use
find . -name ".txt" -exec rm "{}" \;
But why can't I just pipe the results to rm like
find . -name ".txt" | rm
like I would pipe it to grep
find . -name ".txt" | grep a
I've read from somewhere that rm doesn't take input from stdin and therefore I can't pipe it but what does that mean? When I type in rm a.txt it reads from standard input just like I can grep right? Or is there a difference between stdin and command line. Help!
To expand on @Alex Gitelman's answer: yes, there's a difference between "standard input" and the command line.
When you type rm a.txt b.txt c.txt
, the files you list after rm
are known as arguments and are made available to rm through a special variable (called argv
internally). The standard input, on the other hand, looks to a Unix program like a file named stdin
. A program can read data from this "file" just as it would if it opened a regular file on disk and read from that.
rm
, like many other programs, takes its arguments from the command line but ignores standard input. You can pipe anything to it you like; it'll just throw that data away. That's where xargs
comes in handy. It reads lines on standard input and turns them into command-line arguments, so you can effectively pipe data to the command line of another program. It's a neat trick.
For example:
find . -name ".txt" | xargs rm
find . -name ".txt" | grep "foo" | xargs rm
Note that this will work incorrectly if there are any filenames containing newlines or spaces. To deal with filenames containing newlines or spaces you should use instead:
find . -name ".txt" -print0 | xargs -0 rm
This will tell find
to terminate the results with a null character instead of a newline.
However, grep
won't work as before then. Instead use this:
find . -name ".txt" | grep "foo" | tr "\n" "\0" | xargs -0 rm
This time tr
is used to convert all newlines into null characters.