Let's say I have a bunch of photos, all with correct EXIF information, and the photos are randomly named (because of a problem I had). I have a little program called jhead which gives me the below output:

$ jhead IMG_9563.JPG

File name    : IMG_9563.JPG
File size    : 638908 bytes
File date    : 2011:02:03 20:25:09
Camera make  : Canon
Camera model : Canon PowerShot SX210 IS
Date/Time    : 2011:02:03 20:20:24
Resolution   : 1500 x 2000
Flash used   : Yes (manual)
Focal length :  5.0mm  (35mm equivalent: 29mm)
CCD width    : 6.17mm
Exposure time: 0.0080 s  (1/125)
Aperture     : f/3.1
Focus dist.  : 0.29m
ISO equiv.   : 125
Exposure bias: -1.67
Whitebalance : Manual
Light Source : Daylight
Metering Mode: pattern
Exposure Mode: Manual

Now I need to rename all the photos in the folder in the next format:


Where the minor number would be the older image, and the maximum the newer.

I'm not so good scripting, so I'm asking for help.

I think a bash script is enough, but if you feel more comfortable, you can write a python script.

I thought in something like:

$ mv IMG_9563.JPG `jhead IMG_9563.JPG | grep date`

but I don't know how to do that for all the files at once.

  • I don't understand the minor/maximum sentence and the preceding example. Could you clarify this? – maxschlepzig Feb 6 '11 at 22:47
  • That minor/maximum file names is one step up. Because, once I have the files named in a way they represent which is older and newer, I can easily rename to 001.jpg, 002.jpg, etc with another program, although it'd be better to do that with another script. – Tomas Feb 6 '11 at 23:01
  • The "renaming table" would be ls *.JPG | wc > rename And then I'd have to use a script rename to XXX.JPG – Tomas Feb 6 '11 at 23:03
  • Sory, is not wc, I forgot the one to order by name. – Tomas Feb 6 '11 at 23:07
  • The command is sort. – Tomas Feb 8 '11 at 1:06

You can to it for all files using a for loop (in the shell/in a shell-script):

for i in *.JPG; do
  j=`jhead "$i" | grep date | sed 's/^File date[^:]\+: \(.\+\)$/\1/'`.jpg
  echo mv -i "$i" "$j"

This is just a very basic outline. Delete echo when you have verified that everything works as expected.

  • Thanks. Now, grep woudl give me File date : 2011:02:03 20:25:09. How can I filter only the second column? – Tomas Feb 6 '11 at 22:27
  • So I only rename the file as 20:25:09 – Tomas Feb 6 '11 at 22:36
  • @Tomas, updated the answer - if you really only want the time (what about collisions then?) you have to modify the regular expression, of course. – maxschlepzig Feb 6 '11 at 22:45
  • j=`jhead "$i" | grep date | sed 's/.* //'`.jpg instead? – frabjous Feb 6 '11 at 22:45
  • @maxshlepzig: Thanks, you missed a ' before ``.jpg` – Tomas Feb 6 '11 at 22:50

Just found out here that jhead can do it all for you! :)

jhead -autorot -nf%Y-%m-%d_%H-%M-%S *.jpg
  • 7
    Yes! But be careful of that particular format-string, because it'll give two files taken in the same second the same name. %i (or %03i, specifically) will give a sequence number, as requested in the original question. Combining both might not be a bad idea. – mattdm Mar 21 '11 at 17:21
  • Just to respond to mattdm's comment - the ''jhead'' manual suggests it will append an extra number or letter (1,2,3 or a,b,c etc.) to duplicates (identical timestamp) automatically. – John U Sep 18 '18 at 14:16

If someone needs more complex rename, for example to include key exposure values into filename, here is my small script. It renames jpeg files to something like this: NightSky_2014.08.27_22.30.05_NX20_F2.8_f20.0mm_20s_ISO800.jpg.


for s in *.jpg *.JPG
 echo $s
 x=`jhead "$s" | \
 awk 'BEGIN { cmt=""; }
/Camera model/   { c=$4$5$6;} 
/Exposure time:/ { e=$3; 
                   if (e==int(e)) e=int(e); 
                   if (e<1) {e=int(0.5+1/e); e="1T" e "s";} else { e=e "s"; } 
/ISO equiv./     { iso="ISO" $4; } 
/Focal length/   { f="f" $4; } 
/Date.Time /     { d=$3 "_" $4; gsub(":",".",d); }
/Aperture /      { ap=$3; gsub("f/","F",ap); } 
/Comment  /      { cmt=$3 "_"; }
END { print cmt d "_" c "_" ap "_" f "_" e "_" iso ".jpg"; }'`

 echo mv $s $x
 mv $s $x
 echo =====

I liked the code posted by maxschlepzig but had difficulties with the output nonetheless.

The problem was the space in the resulting filename (between the date string and time string). While trivial for anyone using a GUI, it makes file handling on the command-line somewhat more difficult.

Here the 'sed' command has been substantially changed to four separate 'sed' operations in favour of the previous monolithic argument. To suit myself, the following also changes the file to normal 644 permissions.

for i in *.JPG ; do
  chmod 644 $i
  j=`jhead "$i" | grep ^Date/Time | sed -e 's/^Date\/Time[ ]\+: //;s/:/-/g;s/ /_/g':/-/g;s/ /_/g'`.jpg
  echo mv -i "$i" "$j"
  # mv -i "$i" "$j"
  • 2
    Welcome to Stack Exchange.  (1) I understand sed pretty well, so I basically understand what you are trying to do.  But our goal at Stack Exchange is not to hand out fish sandwiches or write thousands of one-off solutions to trivially distinct questions; our goal is to teach people how to fish (i.e., teach people, including the current questioner and future readers, how to solve their own problems).  To that end, your answer would be better if you explained what you are endeavoring.  (Please do not respond in comments; edit your answer to make it clearer.)  … (Cont’d) – G-Man May 31 '15 at 22:08
  • (Cont’d) …  (2) Your command fails because the quotes aren’t balanced.  Please do not post untested code. – G-Man May 31 '15 at 22:08

Because it's easier to deal with (imho), I wrote myself a Ruby script:

require 'fileutils'

ARGV.each { |file|
  if File.exist?(file) && !File.directory?(file)
    exif = `exiftool "#{file}" | grep -E "(Error|Create Date)"`
    if exif.strip.size > 0
      exif = exif.split("\n")[0].split(/\s+/)
      if exif[0] != "Error"
        # Change the target format here
        filename = exif[3].gsub(":", "-") + " " + 
                   exif[4].gsub(":", ".") + 
        if filename != file && !File.exist?(filename)
          FileUtils::mv(file, File.dirname(file) + "/" + filename)

What does this do?

  1. Iterates over all files passed as parameters (e.g. *.JPG).

    I checked that it handles RAW files and videos correctly. It should work with everything exiftool can deal with.

  2. Does not do anything if a file

    • does not exist,
    • is a directory,
    • does not have an EXIF date, or
    • exiftool reports an error, or
    • a file with the target filename already exists.

That makes it quite robust. In particular, no files can vanish (silently) as is the case with some of the other answers.


exiv2 would be an alternative for manipulating, that allows a very simple syntax:

exiv2 is a program to read and write Exif, IPTC, XMP metadata and image comments and can read many vendor makernote tags. The program optionally con‐ verts between Exif tags, XMP properties and IPTC datasets

So this would rename all jpegs in the current folder:

for i in *.JPG; do exiv2 -v -r '%Y%m%d.%H%M%S.:basename:' rename "$i"; done

If you also want to add geo information, you could use exivtool:

exiftool '-filename<${gpslatitude;} ${gpslongitude} ${datetimeoriginal}' -d "%Y-%m-%d %H.%M.%S%%-c.%%e" *.JPG

I like @Kevin's solution, since I wanted to keep the original name as well (avoiding problem with images taken in the same second), here it is my solution:

for i in *.JPG; do jhead -nf%Y%m%d_%H%M%S_"$(basename "$i" .JPG)" "$i" ; done

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.