user650228
user650228

Reputation:

Why does shell redirection interact strangely with find

I want to add a license file to the front of every .java file in a *nix directory and any subdirs. I have this solution, which seems to work fine:

$ cat muppet-license.txt 
// LICENSE: // Manuh-manuh
$ for file in `find . -iname "*.java"`; do 
     cat muppet-license.txt "$file" > "$file.out"; 
     mv "$file.out" "$file";
done

My question is, why does the following find invocation NOT work:

find . -iname "*.java" -exec sh -c 'cat muppet-license.txt "$1" > "$1"' -- {} \;

This causes the very first file found by find to have the "muppet license" repeatedly added to the front of it - the file seems to continually grow without stopping.

Can someone explain what's causing the difference here is? Is it something to do with the modification of the file called $1 causing find to re-find it as part of the recursive search? Does anyone have any good references to the details of the algorithm that find uses?

Upvotes: 0

Views: 90

Answers (3)

Daniel Roethlisberger
Daniel Roethlisberger

Reputation: 7058

You are telling cat to read from the same file as you are writing to:

cat muppet-license.txt "$1" > "$1"

So cat will read muppet-license.txt and write it to $1, then read $1 (which now contains the license) and append the license again to the end of the same file, then continue reading what has been written, and so forth, in an endless loop replicating the license file within $1 over and over...

Something like this should work:

find . -iname "*.java" -exec sh -c 'cat muppet-license.txt "$1" > "$1.out"; mv "$1.out" "$1"' -- {} \;

This avoids the endless loop by writing the license and $1 to a separate file $1.out and moving it back to $1 after writing to it. The difference has nothing to do with find, only with the invocation of sh and cat.

Upvotes: 3

William Pursell
William Pursell

Reputation: 212248

The problem is not with find. The same behavior occurs if you do:

cat muppet-license.txt a.java > a.java

and is because cat is re-reading the data it is writing. The shell opens a.java for writing, truncating it to zero length. cat then writes the contents of muppet-license.txt into a.java, and then opens a.java (which is now a copy of muppet-license.txt), and writes the first line to the end of the file (the 2nd line). It then reads the 2nd line and appends it the end (writing line 3), and repeats continuously.

Upvotes: 2

msw
msw

Reputation: 43497

The problem is that

cat foo $file > $file

will do exactly what you tell it to do, which is not always obvious. The first thing the shell does with that command is opens $file for writing thus truncating it to zero length. Then cat is run, concatenating foo and the contents of $file which are now a copy of foo into $file.

Upvotes: 3

Related Questions