8

I want a bash script which does the following:

  • Find pictures (jpg,jpeg,JPG,JPEG) recursively from current directory downwards
  • Generate a thumbnail with imagemagick's convert
  • Move thumbnail to other directory

My current script looks like this:

for f in `find . -type f -iname "*.jpg"`
  do
  convert ./"$f" -resize 800x800\> ./"${f%.jpg}_thumb.jpg"
  mv ./"${f%.jpg}_thumb.jpg" /home/user/thumbs/
done

It doesn't convert files (or folders with all content) which have spaces/special characters. I tried with print0 but it didn't help.

Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255

3 Answers3

8

Don't iterate over the output of find. The problem you are experiencing is a typical consequence of that.

Your example is a bit tricky due to the file renaming. One not very efficient but safe way to do it is with the -exec option of find, and an additional sh per each file, like this:

find . -type f -iname "*.jpg" -exec sh -c 'echo convert "$1" -resize 800x800\> /home/user/thumbs/"${1%.jpg}_thumb.jpg"' -- {} \;

If you didn't mind using the same name (with .jpg suffix instead of _thumb.jpg), then this simple form would work, and be much more efficient:

find . -type f -iname "*.jpg" -exec echo convert "{}" -resize 800x800\> /home/user/thumbs/"{}" \;

I added echo statements there to check the output before executing the commands. Remove them if the output looks good.

don_crissti
  • 82,805
janos
  • 11,341
  • 2
    @janos, phk and don_crissti: I just accepted the answer from don, but I also upvoted the answers which are working. Thanks for your support guys, you had very interesting/educational answers to my first question on this site :) – LucaTony Dec 27 '16 at 00:06
6

You could use more advanced options like -set combined with percent escapes (namely %t to extract the filename without directory or extension) to do the resize, rename and move of each file with a single convert invocation:

find . -type f -iname \*.jpg -exec convert {} -resize 800x800\> \
-set filename:name '%t' '/home/user/thumbs/%[filename:name]_thumb.jpg' \;
don_crissti
  • 82,805
2

In your solution the file names got split on the default $IFS which includes spaces.

Try the following:

while IFS= read -rd '' f; do

    convert ./"$f" -resize 800x800\> ./"${f%.jpg}_thumb.jpg"
    mv ./"${f%.jpg}_thumb.jpg" /home/user/thumbs/

done < <(find . -type f -iname "*.jpg" -print0)

The find prints the file names separated by null bytes (\0) and using -d '' you set the delimiter of read to the same.

phk
  • 5,953
  • 7
  • 42
  • 71
  • @don_crissti There is a second meaning to -format but I misunderstood it. There is also -set but I don't get its syntax: http://www.imagemagick.org/Usage/files/#save_escapes

    I don't think I will invest any more time into this.

    – phk Dec 26 '16 at 21:58
  • @don_crissti I would feed it all the files as parameters through find […] -exec mogrify […] {} + but apparently it's a bad idea for another reason: "However all three images [in the example] are read into memory, and then modified by the one command. This is not a recommended solution for a number of large images, or very large numbers of images, due to the posibilty [sic] of reaching memory limits and thus going to disk swapping (thrashing)." (from the link posted in my previous comment). Maybe the possibility of such cases is also smth. worth mentioning in your long overview post. – phk Dec 26 '16 at 22:02
  • @phk Your snipped worked too, however I don't know which answer is the better/more efficient one. – LucaTony Dec 26 '16 at 22:51