Find and redirection
The find utility is one of Unix’s most powerful and flexible utilities. But, as I’ve just spent a half hour learning, it can’t do everything.
I was busy on the command line of our primary web server here at the office, and needed a quick command to clear out log files. Seemed simple enough. I tried (variations of)
$> find . -type f -name "*.log" -exec cat /dev/null > {} \;
I’ll break it down, in case you’re unfamiliar with find. find, at it’s most basic, begins at the specified target location, and recursively descends, printing all filesystem records it sees on it’s way downward (I tell it “current directory” with the “.”). -type f specifies that only regular files are to be considered in the output, and no directories, symlinks, pipes, etc.. -name "*.log" tells find that I only want entries matching the shell expansion *.log.
find is a great utility if you stop there. But the -exec command is the real gold. -exec tells find not to print the entires it locates to stdout, but rather to execute the given command for each file found. For example, if I did the following…
$> mkdir d
$> touch d/bar
$> touch d/foo
Then
$> find d -exec command -o {} \;
would be equivalent to the following
$> command -o d
$> command -o d/bar
$> command -o d/foo
Find runs command, with option o, on each viable entry found, replacing {} with the name of the file.
So, when I tried
$> find . -type f -name "*.log" -exec cat /dev/null > {} \;
I figured that for every file ending in .log, find would overwrite it’s contents with a null string. Not so. The result of the above command, was that, in my current working directory, I had a file called “{}” and my logs were untouched. I figured I had the solution in the bag, so to speak.
$> find . -type f -name "*.log" -exec cat /dev/null \> {} \;
Just escape the redirection character right? Nope. Try and try as I might, I couldn’t get it to work. Finally, I gave up and implemented
$> find . -type f -name "*.log" -exec cp /dev/null {} \;
Which is the same effect as desired. but I was still bothered. Upon further research, it turns out that find just can’t redirect by itself, so if you need something like that, you’ll have to resort to invoking extra shells or interpreters, like so
$> find . -type f -name "*.log" -exec sh -c "cat /dev/null > {}" \;
This works on my box and on our web server (One’s Debian, one’s Fedora, both using bash) however from what I’ve been reading, this isn’t portable, so your mileage may vary. (Specifically, the issue lies around find [not] being able to properly replace the value of {} when nested inside of quotes and things)