Search code examples
bashfor-loopif-statementconvertersmodifier

How to loop multiple extensions with upper and lower case letters using for command


I am trying to loop a set of jpg images in a folder. The problem is some of the images have the extension in all lower case letters (.jpg) and others have all capital letters (.JPG).

I am trying to use variable modifiers to change the extensions as the loop progresses.

I figured out how to phrase the for var in LIST part but I can't get the modifiers to replace the extensions correctly.

Essentially I need the jpg files to convert to files with .mpc and .cache extensions.

I end up with files that have names like FILE.jpg.mpc or FILE.JPG.jpg when I need the file to be FILE.mpc and FILE.cache

Here is my function that works great if you only use for i in *.jpg but when you add for i in *.{jpg,JPG} everything falls apart.

Again here is what I have so far.

imow()
{

    clear

    local i DIMENSIONS RANDOM

    # find all jpg files and create temporary cache files from them
    for i in *.{jpg,JPG}
    do
        # create random direcotories in case you are running this function more than once at the same time. it prevents cross-over.
        RANDOM="$(mktemp --directory)"
        "${RANDOM}" 2>/dev/null
        echo -e "\\nCreating two temporary cache files: ${RANDOM}/${i%%.jpg,JPG}.mpc + ${RANDOM}/${i%%.{jpg,JPG}}.cache\\n"
        DIMENSIONS="$(identify -format '%wx%h' "${i}")"
        convert "${i}" -monitor -filter 'Triangle' -define filter:support='2' -thumbnail "${DIMENSIONS}" -strip \
        -unsharp '0.25x0.08+8.3+0.045' -dither None -posterize '136' -quality '82' -define jpeg:fancy-upsampling='off' \
        -define png:compression-filter='5' -define png:compression-level='9' -define png:compression-strategy='1' \
        -define png:exclude-chunk='all' -auto-level -enhance -interlace 'none' -colorspace 'sRGB' "${RANDOM}/${i%%.{jpg,JPG}}.mpc"
        clear
        for i in "${RANDOM}"/*.mpc
        do
            if [ -f "${i}" ]; then
                echo -e "\\nOverwriting original file with optimized self: ${i} >> ${i%%.mpc}.jpg\\n"
                convert "${i}" -monitor "${i%%.mpc}.jpg"
                if [ -f "${i%%.mpc}.jpg" ]; then
                    mv "${i%%.mpc}.jpg" "${PWD}"
                    rm -fr "${RANDOM}"
                    clear
                else
                    clear
                    echo 'Error: Unable to find the optimized image and therefore can'\''t overwrite the original.'
                    echo
                    exit 1
                fi
            fi
        done
    done
}

Like I mentioned if you run it with just the .jpg extension it works great. here is a working example.

imow()
{

    clear

    local i DIMENSIONS RANDOM

    # find all jpg files and create temporary cache files from them
    for i in *.jpg
    do
        # create random directories in case you are running this function more than once at the same time. it prevents cross-over.
        RANDOM="$(mktemp --directory)"
        "${RANDOM}" 2>/dev/null
        echo -e "\\nCreating two temporary cache files: ${RANDOM}/${i%%.jpg}.mpc + ${RANDOM}/${i%%.jpg}.cache\\n"
        DIMENSIONS="$(identify -format '%wx%h' "${i}")"
        convert "${i}" -monitor -filter 'Triangle' -define filter:support='2' -thumbnail "${DIMENSIONS}" -strip \
        -unsharp '0.25x0.08+8.3+0.045' -dither None -posterize '136' -quality '82' -define jpeg:fancy-upsampling='off' \
        -define png:compression-filter='5' -define png:compression-level='9' -define png:compression-strategy='1' \
        -define png:exclude-chunk='all' -auto-level -enhance -interlace 'none' -colorspace 'sRGB' "${RANDOM}/${i%%.jpg}.mpc"
        clear
        for i in "${RANDOM}"/*.mpc
        do
            if [ -f "${i}" ]; then
                echo -e "\\nOverwriting original file with optimized self: ${i} >> ${i%%.mpc}.jpg\\n"
                convert "${i}" -monitor "${i%%.mpc}.jpg"
                if [ -f "${i%%.mpc}.jpg" ]; then
                    mv "${i%%.mpc}.jpg" "${PWD}"
                    rm -fr "${RANDOM}"
                    clear
                else
                    clear
                    echo 'Error: Unable to find the optimized image and therefore can'\''t overwrite the original.'
                    echo
                    exit 1
                fi
            fi
        done
    done

Solution

  • As I read it, your function already overwrites original .jpg files, but hardcodes a lowercase .jpg which leaves the old .JPG files lying around. I'd just formalize the conversion to lowercase unless there's some specific reason you shouldn't/can't.

    Get rid of those broken uppercase variables, and your inner loop using the same loop var as the outer loop, etc. I moved some stuff around, but don't have imagemagick installed so this is a freehand edit; I trust someone will point out any glaring logic errors as I can't really test it the way I'd like.

    I explicitly test the imagemagick return code to simplify a bit, and added a test to make sure the mv succeeded. If the original file's extension is not all lowercase, it now should be removed once the optimized file is staged.

    I also removed the clear commands that were erasing output you had just explicitly logged in favor of sending convert spam to logs that get wiped unless there was a problem.

    imow() {
      local orig mpc new dim rc tdir boilerplate
      tdir="$(mktemp --directory)" 
      boilerplate=( -monitor -filter 'Triangle' -define filter:support='2' 
        -strip -unsharp '0.25x0.08+8.3+0.045' -dither None -posterize '136'
        -quality '82' -define jpeg:fancy-upsampling='off' 
        -define png:compression-filter='5' -define png:compression-level='9'
        -define png:compression-strategy='1' -define png:exclude-chunk='all'
        -auto-level -enhance -interlace 'none' -colorspace 'sRGB' )
      for orig in *.[Jj][Pp][Gg]
      do dim="$(identify -format '%wx%h' "$orig")"
         if convert "$orig" "${boilerplate[@]}" -thumbnail "$dim" "${tdir}/${orig%.???}.mpc" > "$tdir/convert-mpc.out"
         then for mpc in "${tdir}"/*.mpc
              do new="${mpc%.mpc}.jpg"
                 if convert "$mpc" -monitor "$new" > "$tdir/convert-jpg.out"
                 then echo "Replacing $orig with optimized version $new"
                      if mv "$new" "$PWD"
                      then [[ "$orig" == "$new" ]] || rm -f "$orig"
                           rm -fr "${tdir:?safety check}/*.*" # can't expand to rm -fr /*
                      else rc=$?;
                           echo "Cannot mv '$new' to '$PWD': error $rc; aborting" >&2
                           exit $rc
                      fi
            else rc=$?
                      echo "convert error $rc on $mpc; c.f. $tdir - aborting." >&2
                      exit $rc
                 fi
              done
         else rc=$?
              echo "convert error $rc on $orig; c.f. $tdir - aborting." >&2
              exit $rc
         fi
      done
      rm -fr "$tdir"
    }
    

    *.[Jj][Pp][Gg] with match any combination of upper/lower on the extension.

    Make sure you test and modify to suit your needs.
    Good luck.

    addendum

    I have discovered that changing the line if convert "${mpc}" -monitor "${new}" > "${tdir}/convert-jpg.out" to if convert "${mpc}" -monitor "${new}" > "${tdir}/convert-jpg.out" &> /dev/null GREATLY speeds up the parsing of each loop. Otherwise cheers.

    Please c.f. the GNU bash guide on Redirections

    Observe:

    $: date >foo
    $: cat foo
    Fri Feb  3 12:33:13 CST 2023
    $:
    

    whereas:

    $: date >foo &> /dev/null
    $: cat foo
    $:
    

    in the second, date's output is redirected to foo, which opens (and creates or truncates) the file, but then &> redirects both stdout and stderr to /dev/null, overriding the first redirection to a logfile, so foo is empty after it runs.

    Maybe it was "significantly faster" because it failed?
    How would you know?

    IMHO, never throw away your logs, especially stderr, even if you are testing your return. When it eventually does fail, you have nothing to debug. I usually clean up the logs after a success, but on a fail I usually cat them, then and abort.

    Worst case, even if you do throw them away, remove the redundancy.
    Assuming all use

    if convert "${mpc}" -monitor "${new}" 
    

    followed by some output control or other, rather than

     > "${tdir}/convert-jpg.out" &> /dev/null # log will always be empty
    

    If you just do not want the stdout at all, try one of these -

    Leaving stderr defaulting to the console,

    > /dev/null                              # stdout to the bitbucket
    >&-                                      # just *close* stdout
    

    redirecting stderr to a log,

    2> "${tdir}/convert-jpg.out" > /dev/null # stdout to the bitbucket
    2> "${tdir}/convert-jpg.out" >&-         # stdout closed
    

    if stdout has any value -

    &> "${tdir}/convert-jpg.out"             # both to a specified log
    

    or worst case, if you just insist,

    &>/dev/null                              # BOTH to the bitbucket
    &>-                                      # close both