3

I have a simple bash script running a command in ImageMagick. It takes an image (INFILE - typically a .tif), resizes it, and saves the result (OUTFILE - typically a .jpg)

INFILE=/path/'image name.tif'
OUTFILE=/path/'image name.jpg'

echo INFILE="${INFILE}" OUTFILE="${OUTFILE}" 

magick -quiet "${INFILE}" \
  -filter LanczosRadius \
  -distort resize 1600 \
  -quality 92 \
  "${OUTFILE}"

The name of this script is Magick-Resize.sh

I would like to batch process a number of images using this script - that is, run it on multiple INFILES. Eg. If I have 100 images in a folder, I would like each of them to be processed once by this script, producing a unique OUTFILE for each.

I am very much a novice in bash. I gave it a crack with the below:

INSCRIPT=/path/Magick-Resize.sh

echo INSCRIPT="${INSCRIPT}"

for INFILE in *.tif; do
    if [[ "$INFILE" = *-tagged.tif ]]; then
        continue # skip output
    fi
    name=$(basename "$INFILE")-tagged.jpg
    echo name="${name}"
    "${INSCRIPT}" "$INFILE" > "${name}"
done

But in a test folder containing two .tif images, all it output was one image with the filename "*.jpg"

Is anyone able to provide a solution?

I'm on Mint 20.3

3 Answers 3

4

Your script doesn't take any argument, the names of the INFILE and OUTFILE are hardcoded within.

Here, I'd use zsh instead of bash, and do:

transform()
  magick -quiet $1 \
   -filter LanczosRadius \
   -distort resize 1600 \
   -quality 92 \
   $2

autoload -Uz zmv
zmv -P transform './(^*-tagged).tif' './$1-tagged.jpg'
3
  • Don't you need to quote the positional arguments inside the function?
    – Quasímodo
    Aug 6 at 12:51
  • @Quasímodo, not in zsh as file names can't be empty strings. Quoting wouldn't harm in any case Aug 6 at 15:24
  • I marked a different response as 'answer' below since it was in bash and allows me to keep the little bash syntax I know to batch other more complex tasks - but for the question asked this solution works perfectly. Thank you.
    – ItHertz
    Aug 7 at 5:45
2

I show another way to solve the problem: to modify the original code using bash.

  • make one single shellscript
  • put a shebang at the top, #!/bin/bash, to control which shell should run the script
  • make a function instead of a separate shellscript of the 'doer' part.
  • switch from magick to convert because that is how the Image Magick tool is implemented in my system. You can easily switch back to run it in your system.
  • tweak the batch processing part to make it more robust.
    • add some # comments to help understanding what happens. When tagged files are encountered, skip, otherwise create a tagged jpg file
    • the bash variables for the file names are manipulated and tested in order to create only the desired files. This is described in man bash at the chapter about Parameter Expansion.

Please notice how the function doer is called with two parameters, and how they are seen as $1 and $2 in the function.

#! /bin/bash -

function doer() {
  INFILE="$1"
  OUTFILE="$2"
  #echo INFILE="${INFILE}" OUTFILE="${OUTFILE}" 
  convert -quiet "${INFILE}" \
    -filter LanczosRadius \
    -distort resize 1600 \
    -quality 92 \
    "${OUTFILE}"
}

#######
# main
#######

shopt -s nullglob
files=(./*.tif)
if (( ${#files[@]} )); then
  # there is at least one tif file, so continue
  for INFILE in "${files[@]}"; do
    if [[ "$INFILE" != *-tagged.tif ]]; then
      # not a tagged tif file, so create variable 'name'
      name="${INFILE%.tif}-tagged.jpg"
      if ! test -s "$name"; then
        # not a tagged jpg file, so create file 'name'
        printf '%s\n' "${name}"
        doer "$INFILE" "${name}"
      fi
    fi
  done
else
  echo >&2 "no tif files found"
  exit 1
fi
echo "normal termination"
5
  • 1
    ls *.tif > /dev/null is not a valid test to check that a glob matches at least one file. ls -d -- *.tif > /dev/null would be slightly better, but still not fully correct. In bash, you'd do shopt -s nullglob; files=(*.tif); if (( ${#files[@]} ))... Aug 6 at 17:05
  • 1
    ${INFILE/.tif/-tagged.jpg} would change blah.tiffany.tif to blah-tagged.jpgfany. You can do ${INFILE/%.tif/-tagged.jpg} for the .tif to be matched only at the end, though you could also use the standard (also from ksh) ${INFILE%.tif}.jpg. Similarly, why replace the OP's correct [[ "$INFILE" = *-tagged.tif ]], with that not foolproof and convoluted [ "$INFILE" == "${INFILE/-tagged.tif}" ] ? Aug 6 at 17:11
  • @StéphaneChazelas, I have tried to follow your advice, and it works when I tested it 'at home'. Please check if I fixed it correctly.
    – sudodus
    Aug 6 at 17:47
  • @StéphaneChazelas, I notice that there were several more things to modify. Thanks for teaching how to make a foolproof script. Maybe it has reached the highest level now, guruproof ;-)
    – sudodus
    Aug 6 at 19:01
  • Thanks very much, this works perfectly!
    – ItHertz
    Aug 7 at 5:42
0

I think this should be enough for your needs:

First enable extglob by running: shopt -s extglob and then

for i in !(*-tagged).tif; do 
    magick "./$i" <flags> "./${i%%.tif}-tagged.jpg"; 
done

This can also be trivially parallelized with GNU parallel:

parallel magick ./{} <flags> ./{.}-tagged.jpg ::: !(*-tagged).tif
2
  • See When is double-quoting necessary?. You'd also need a ./ prefix to the glob to avoid problems with files whose name starts with -. Aug 7 at 6:33
  • I probably missed the double quotes when I was typing the command here. I will also add the prefix, although that's a very unusual edge case.
    – r_31415
    Aug 7 at 7:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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