I have a directory of jpg images. Is there a shell script (bash or zsh would be acceptable) that would move all landscape images into a directory, and all portrait images into another directory?

  • You mean landscape="width>height" and portrait="width<height", right? You don't mean landscape="hills, forests and lakes" and portrait="people posing as if for a photo". Just checking... – Kusalananda Jul 7 '16 at 7:10
  • 1
    @Kusalananda Now that you mention it, detecting images with hills, forests, and lakes would be an interesting problem too. – Satō Katsura Jul 7 '16 at 11:13
  • 2
    @SatoKatsura, perhaps we could use facial recognition software to determine if a photo was indeed a portrait. But what if the hills have eyes? – Raphael Rafatpanah Jul 7 '16 at 12:54
up vote 7 down vote accepted

You could use imagemagick's identify with the fx special operator to compare height and width e.g. h>w. If true it will output 1, if false it will output 0:

for f in ./*.jpg
do
  r=$(identify -format '%[fx:(h>w)]' "$f")
  if [[ r -eq 1 ]] 
  then
      mv "$f" /path/to/portraits
  else
      mv "$f" /path/to/landscapes
  fi
done

With zsh you could use the estring glob qualifier (to select only the files for which the code between quotes returns true) and do something like:

mv ./*.jpg(.e_'identify -format "%[fx:(h>w)]" $REPLY | grep 0 >/dev/null'_) /path/to/landscapes
mv ./*.jpg /path/to/portraits

so the first command moves all landscape images to /path/to/landscapes and the second one moves the remaining images to /path/to/portraits.


The above solutions will treat square images as landscapes and move them into the respective directory. You could introduce a second condition if you wanted to move them to their own directory:

mv ./*.jpg(.e_'identify -format "%[fx:(h<w)]" $REPLY | grep 1 >/dev/null'_) /path/to/landscapes
mv ./*.jpg(.e_'identify -format "%[fx:(h>w)]" $REPLY | grep 1 >/dev/null'_) /path/to/portraits
mv ./*.jpg /path/to/squares

or you could use a different condition (e.g. check h/w ratio) to separate the square images from the rest and leave them in the current directory:

for f in ./*.jpg
do
  r=$(identify -format '%[fx:(h/w)]' "$f")
  if [[ r -gt 1 ]] 
  then
      mv "$f" /path/to/portraits
  elif  [[ r -lt 1 ]]
  then
      mv "$f" /path/to/landscapes
  fi
done

Using identify from ImageMagick:

#! /bin/sh                                            
identify -format '%w %h %i\n' -- "$@" 2>/dev/null | \
    while read W H FN; do
        if [ $W -gt $H ]; then
            echo mv -f -- "$FN" /path/to/landscape/
        else
            echo mv -f -- "$FN" /path/to/portraits/
        fi
    done

This is not particularly efficient because it runs mv for each file, but you didn't ask for efficiency.

This uses the fileinfo utility to get an image's width and height. If the height is greater than the width, the file is moved to the portraits/ directory. If not, it is moved to the landscape/ directory.

for f in ./*jpg
do
    if fileinfo "$f" 2>&1 | awk '/w =/{w=$3+0; h=$6+0; if (h>w) exit; else exit 1}'
    then
        mv "$f" portraits/
    else
        mv "$f" landscape/
    fi
done

The file name in this loop is double-quoted where needed so that this loop is safe to use even for image file names with spaces, newlines, or other difficult characters.

On a debian-like system, fileinfo can be installed via:

apt-get install leptonica-progs

Other similar utilities can be used as long as the awk command is modified appropriately.

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

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