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.

Any help to get this script to find every file(jpg,jpeg,JPG,JPEG) or even a better method is highly appreciated!

share|improve this question
up vote 4 down vote accepted

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' \;
share|improve this answer

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.

share|improve this answer
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 7 hours ago

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.

share|improve this answer
    
@don_crissti There is a second meaning to -format but I misunderstood it. There is also -set but I don't get its syntax: imagemagick.org/Usage/files/#save_escapes I don't think I will invest any more time into this. – phk 9 hours ago
    
@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 9 hours ago
    
@phk Your snipped worked too, however I don't know which answer is the better/more efficient one. – LucaTony 8 hours ago
    
@LucaTony The solution by don_crissti in the accepted answer probably is. – phk 8 hours ago

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.