find and xargs

How hard can it be to send a bunch of filenames from find as arguments to a shell command? It turns out that it's much more difficult than it should be if you want to add another argument after the list of filenames.

The problem today was that my colleague and I wanted to find all images with a particular name and send them to Imagemagick to put them all together. The Imagemagick command for that looks like
montage -size 1 -geometry 66x50 image*.png montage.png

The issue is that Imagemagick isn't flexible. The output filename has to be the last argument and the input files can't be passed by standard input. The contenders for solving the problem are find's -exec option and xargs.

So the first thing we tried was this:
find . -name "image*.png" -exec montage -size 1 -geometry 66x50 {} montage.png +
but this gives a "missing argument to `exec'" error -- find doesn't allow extra arguments after the placeholder.

Then we tried using xargs:
find . -print0 -name "image*.png" | xargs -0 -I {} montage -size 1 -geometry 66x50 {} montage.png
but this runs the command once for each input unless we increase xargs's -L (max input arguments per command) option back up from 1 (which is set by using the -I option -- no idea why) to some arbitrary large number. This seems like a bad way to do things.

The solution we came up with was to add an extra argument before the stream gets to xargs:
find . -print0 -name "image*.png" | cat - <(echo -ne "montage.png\0") | xargs -0 montage -size 1 -geometry 66x50

This is using process substitution to put montage and a null character as a file descriptor input to cat, which adds that to the end of the first argument, its standard input.

It's still a mess -- any better solutions?

1 comment:

  1. At least for the xargs in GNU findutils 4.4.2, adding a -L actually causes xargs to completely ignore the -I switch... sigh. Thanks for the tip, it led me to my similar solution:

    echo tiled.png | cat filenames.tmp - | xargs montage -tile 4x

    filenames.tmp is a file containing all the tile filenames in one line (I needed the tiles in a very specific order, so I couldn't use find like you).