I wrote a script that converts images in a folder. The script uses a for loop:

for file in *.jpg; do
     outputFile=$(echo "image"$(echo $i))
     convert "$file" -resize 50x50 $outputFile

What I want to do is allow the script to run on multiple file type extensions. Now I've tried doing:

for file in *.jpg *.JPG *.jpeg; do

The problem I have with this is that if you are in a folder with all *.JPG and no *.jpg, the script still bumps i+1 even though there was no images, because it runs the for loop anyways, upon not finding any *.jpg it goes onto *.JPG.

How can I target multiple file extensions without it running itself per type? Example, is their syntax something like this:

for file in [*.jpg | *.JPG]; do

That way my output folder always contains images labeled like this:

Instead of ending up with a folder missing image1.jpg because it had no *.jpg to run on first.

  • ...but what about to use an if [ -e "$file" ]...fi and execute conversion and increment only when the file exists? (You can use other tests to check for link etc etc). It should protect even in case you have a directory named Jpg.
    – Hastur
    Commented Feb 5, 2016 at 10:09

3 Answers 3


The Solution

Use nullglob. Put the following line before the for loop:

shopt -s nullglob

nullglob means that if there is no such file, the glob is removed from the list. Observe without nullglob:

$ echo  *.jpg *.JPG *.jpeg
test.jpg *.JPG *.jpeg

Now, with nullglob:

$ shopt -s nullglob
$ echo  *.jpg *.JPG *.jpeg

Revised Script

The complete script could be:

shopt -s nullglob
for file in *.jpg *.JPG *.jpeg; do
     convert "$file" -resize 50x50 "image$i.jpg"

Three notes:

  1. In the definition of outputFile, all those echo statements were unnecessary.

  2. Following convention, I added an extension, .jpg, to your output file name. If you really don't want the extension, just remove it.

  3. I replace $[...] with its more standard form: $((...)).

  • 1
    A more reliable way to find all JPEGs in the current directory: file * | grep 'JPEG image data' | cut -d: -f1
    – Dummy00001
    Commented May 9, 2015 at 13:25
  • @Dummy00001:  Of course that fails if you have file(s) whose name(s) contain “JPEG image data” or ‘:’. Commented Feb 21, 2023 at 22:35
  1. Since you want to look for multiple distinct (case insensitive) extensions, John1024’s answer featuring shopt -s nullglob is a very good one to use.

  2. If you were simply looking for *.jpg, *.JPG, and the other six various combinations of upper case and lower case, the easiest thing to do would be

     for file in *.[Jj][Pp][Gg]

    which looks for files whose names are

    • (something), followed by
    • . (a period), followed by
    • J or j, followed by
    • P or p, followed by
    • G or g

    in other words,

    • (something), followed by
    • . (a period), followed by
    • "jpg", in some combination of upper case and lower case
  3. So one way to solve your problem is

     shopt -s nullglob
     for file in *.[Jj][Pp][Gg] *.[Jj][Pp][Ee][Gg]
     do …
  4. The […] trick is a good one to know, but there is a better way.  If I told you that there was a shell option called nocaseglob, could you guess what it does?

     shopt -s nullglob nocaseglob
     for file in *.jpg *.jpeg
     do …

    so now *.jpg means

    • (something), followed by
    • . (a period), followed by
    • "jpg", in some combination of upper case and lower case

    and so the above command will match *.jpg, *.JPG, *.Jpg, *.jpeg, *.jPeG, etc.

  5. Another way is to use the extglob shell option to enable extended pattern matching operators.  You'll still want nullglob and nocaseglob.  The extended pattern matching operators all look like

    (some_special_character)  (  pattern-list  )

    where spaces are added for clarity, and pattern-list is a list of patterns separated by | character(s).  The one that looks most interesting for your purposes is


    which means match one of the listed patterns.  So now the script becomes

     shopt -s nullglob nocaseglob extglob
     for file in *.@(jpg|jpeg)
     do …

    This may look like a little more typing, but the tradeoff shifts as the number of extensions grows.  Consider,

     for file in *.@(jpg|jpeg|jfif|exif|tif|tiff|gif|bmp|png|svg)

    is a bit shorter than

     for file in *.jpg *.jpeg *.jfif *.exif *.tif *.tiff *.gif *.bmp *.png *.svg
  6. Where did you learn to do $(echo …)?  Sure, $(some_command(s) …) can certainly be useful, and maybe even $(echo …) sometimes, but you're overusing it.  It's perfectly good to say


    and you probably don't even need the quotes (in the assignment statement).

  7. OK, you can be excused just this once, since you know that the value of outputFile is benign, since you set it from a constant string of non-blank, non-special characters (image) plus a variable which you set to a constant string of non-blank, non-special characters (1) and did only trivial arithmetic operations on it.  But, as a general rule, you should always quote all reference to shell variables, unless you have a very good reason not to and you're sure you know what you're doing.  So your convert command, ideally, should be

     convert "$file" -resize 50x50 "$outputFile"

    (with quotes around $outputFile).


You would want to replace this part:

for file in *.jpg; do

By this:

for file in $(ls *.jpg *.JPG); do

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .