1

I have a directory which includes multiple sub-directories. I would like to go through the directories and subdirectories and find the jpg files and convert the size using mogrify command. I would like to do it as dynamic as possible that's why I wrote a script. The $1 is the first argument that I pass through when executing the bash script. After running the script, it gives me an error about 'mogrify can not read [@]%'. I guess something is very wrong with my code and I am not mature in bash. Can anyone tell me how to do this script dynamically so that would be fast.

p.s: the name of jpg files are not in especial format...just bunch of numbers.

for folder in $1/*
do
    for file in "$folder"/*
    do
        if [  -e "${file[@]%.jpg}" ]; then
            mogrify -resize 112x112! "${file[@]%.jpg}"
        fi

    done
done
2
  • what do you think ${file[@]%.jpg} should emit? you might just need mogrify -resize 112x112! "${file}", but hard to be sure as you haven't specified what you need to work with mogrify. AND you're hoping for your process to be fast; don't get hung up on that, it is more important that your code doesn't cause any unfixable errors. Once you are certain about the safety of your code, then you can think about "what can it do to make it run faster", but given the constraints of your project, a shell for loop (what you have) is likely as fast as you'll be able to achieve (IHMO). Good luck.
    – shellter
    Commented Nov 29, 2019 at 22:08
  • The [@] bit is normally used to get all elements of an array, but file isn't an array, so this doesn't make sense. Also, the %.jpg part means "remove '.jpg' from the end", so if your script finds somefolder/12345.jpg, it'll try to run mogrify on somefolder/12345 instead. Commented Nov 29, 2019 at 22:16

3 Answers 3

8

If you're open to using find, then this becomes pretty easy:

#!/usr/bin/env bash
find "$1" \( -iname \*.jpg -o -iname \*.jpeg \) -print0 | while read -r -d $'\0' file; do
  # base="${file##*/}" $base is the file name with all the directory stuff stripped off
  # dir="${file%/*}    $dir is the directory with the file name stripped off
  mogify -resize '112x112!' "$file"
done

Put that in a file named mymog.bash then

$ chmod 755 mymog.bash
$ mymog.bash /some/dir

Notes:

  • ! is special to bash, so putting that in the single quotes make it "unspecial", passing it along to the mogrify command unmolested.
  • The double quotes around $1 and $file are needed in case a directory or file name has spaces in it. If you had a directory named /Users/alice/my pictures and didn't use the quotes, mogrify would get one argument named /Users/alice/my and another one named pictures.
  • Make sure you use the \( and \) for find. That makes the whole condition ("match *.jpg" OR "match *.jpeg") apply to the action -print0.
  • I used find 's -print0 action which prints each matching file name with a null-terminated (zero-terminated) string. You can have filenames that have newline characters in the middle. This protects against that.
  • bash 's built-in read command reads until a newline by default. I used the -d $'\0' to make it read each "line" or "record" (filename) up to the null (zero) character at its end. (Each ends with null because of the -print0.)

This solution (one of many) has two parts:

  1. It uses the find utility to find (under the directory given) all files that end in .jpg or .jpeg, ignoring the case of the filenames. [So it will match .JPG or even \.JpEg.]
    • It spits out one record for each file.
    • If you give it an absolute path like /some/dir, it will find /some/dir/a.jpg and /some/dir/sub1/sub2/sub3/b.jpg.
    • If you give it a relative path like ../../nearby/dir, it will find ../../nearby/dir/c.jpg and ../../nearby/dir/sub1/sub2/sub3/d.jpeg.
  2. The find part ends with the first | on that line. After that, it is a bash whiledo loop.
    • The variable file takes on the value of each record spit out by find.
    • The loop (everything between do and done) runs once for each value that file takes on.
    • The two rows that start with # are comments. They contain commands that are ignored (skipped). You can remove the # to have bash run those commands too. I included them as examples in case you needed the directory part or just the filename part of the record.
3
  • thanks for your exact explanation. What is file in the while read -r -d $'\0' file ? Is it referring to jpg files? I just want to make sure I am getting it right Commented Nov 29, 2019 at 23:16
  • 1
    @user8034918 The find command outputs a list of .jpg and .jpeg files; the while read ... file part reads items (one at a time) from that list, and for each one it puts the filepath in the file variable and executes the mogrify command. So yes, file winds up referring to jpg files. Commented Nov 29, 2019 at 23:30
  • Thanks, @GordonDavisson. I edited the answer to add a little more explanation. I hope it helps. Commented Nov 30, 2019 at 1:20
3
find "$1" -type f -name "*.jpg" -exec mogrify -resize 112x112! {} \;
0

Try find and a while read loop:

 find "$1" -type f -name '*.jpg' -print | while read fname
 do
    ....
 done

If your filenames may contain special chars like line feed, the use:

find "$1" -type f -name '*.jpg' -print0 | while IFS= read -r -d '' fname
do
    ....
done

There are many more options to tune the search.

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