MWB
MWB

Reputation: 12567

Why doesn't "rm -i" work in a "while read" looping over the output of "find"?

If I execute

find . -name \*\.txt | while read f; do /bin/rm -i "$f"; done

rm asks:

/bin/rm: remove regular empty file ‘./some file name with spaces.txt’?

but the command exits without waiting for the answer. Why is that and how to fix it?

The other question on this subject, https://unix.stackexchange.com/questions/398872/rm-ir-does-not-work-inside-a-loop loops through the ls output, but in my case STDIN is the output of find, with multiple files, each potentially with spaces in them, so I can't switch to non-loop approach.

Upvotes: 0

Views: 414

Answers (3)

Aaron
Aaron

Reputation: 24802

Instead of looping over the content produced by the default -print action of find, use find's -exec action :

find . -name \*\.txt -exec rm -i -- {} +

In this command, {} represents the elements iterated over by find, and the + both delimits the command executed by find -exec and states that it should replace {} by as many elements it can at once (as an alternative you can use \; for the command to be executed once per element). -- after rm -i makes sure the file listed by find won't be interpreted as rm options if they start by a dash but correctly as filenames.

Not only is this more concise (although not more easily understandable) , but it also avoids problems related to special characters naïve solutions would have.

Upvotes: 2

Charles Duffy
Charles Duffy

Reputation: 295530

while IFS= read -r -d '' f <&3; do
  rm -i -- "$f"
done 3< <(find . -name '*.txt' -print0)
  • Put the content on a different file descriptor than the one read -i uses for input. Here, we're using FD 3 (3< on the redirection, and <&3 on the read alone).
  • Clear IFS, or leading and trailing spaces in your filenames will be stripped.
  • Pass -r to read, or literal backslashes in your filenames will be consumed by read rather than placed in the populated variable.
  • Use NUL-delimited streams, or filenames containing newlines (yes, they can happen!) will break your code. To do so, use -print0 on the find side, and -d '' on the read side.

Upvotes: 4

iBug
iBug

Reputation: 37267

Because rm inherits its standard input from the loop, which is connected to the output from the left side. So after read f got the name foo, there's nothing for rm to read from stdin. That's why rm exits on its own.

Don't use pipe if you need rm -i the prompt. There are many alternatives. One of them is

rm -i $(echo foo)

Upvotes: -1

Related Questions