Bash – Batch rename image files by age plus add date and variable to filename

bashdategawkrename

The question is completely rewritten due to things learned from the first two answers and comments that I was unaware of when first asking.


After a photo shoot I come home with files that look like _DSC1234.NEF. NEF is Nikon's camera raw file format, so EXIF data is present in the files.

I would like to automatically rename them with three parts:

  1. Creation date in the format YYYYMMDD
  2. Name of Shoot
  3. Number of Image

So the final file name should look like this

20140707_NameOfShoot_0001.NEF

There are a few issues:

ad 1. Creation date
sometimes I can only copy and rename the files a few days after shooting, so the date should reflect the date the picture was taken, not the date it was copied. mtime seems like the best bet here or if possible creation date from EXIF.

ad 2: Name of Shoot
This would ideally be a variable I could set as a parameter on calling the script.

ad 3. Number of Image
This should reflect the age of the image, the oldest having the lowest number. The problem is that cameras usually restart numbering at 0000 after they hit 9999. So 9995-9999 can potentially be older than 0000-0004. I am looking for a solution that reflects file age and in this special case would rename

  • _DSC0000.NEF -> 20140707_FOO_0004.NEF
  • _DSC0001.NEF -> 20140707_FOO_0005.NEF
  • _DSC0002.NEF -> 20140707_FOO_0006.NEF
  • _DSC9997.NEF -> 20140707_FOO_0001.NEF
  • _DSC9998.NEF -> 20140707_FOO_0002.NEF
  • _DSC9999.NEF -> 20140707_FOO_0003.NEF

Again, either mtime or if possible creation date from EXIF seem right.


From here I have a working solution which renames all .NEF-files in a folder by date:

find -name '*.NEF' | 
gawk 'BEGIN{ a=1 }{ printf "mv %s %04d.NEF\n", $0, a++ }' | 
bash 

Hard coding file-modification-date and the shootname works well, but it would be great if it could be automated.

For the timestamp I find articles on strftime(), mktime(), systime() but I don't understand how to use them to return file modification date. I also tried to add DATE=$(date +"%Y%m%d") and add $DATE to the gawk-line, which leads to deletion of all files in current folder (and probably is systime anyway and not change time).

For the variable, I tried

gawk 'BEGIN{ a=1 }{ printf "mv %s $1_%04d.NEF\n", $0, a++ }' | 

and call the script with ./rename FOO, but FOO is ignored in renaming.

Best Answer

Most unices don't track a file's creation date¹. “Creation date” is ill-defined anyway (does copying a file create a new file?). You can use the file's modification time, which is by a reasonable interpretation the date at which the latest version of the data was created. If you make copies of the file, make sure to retain the modification time (e.g. cp -p or cp -a if you use the cp command, not bare cp).

A few file formats have a field inside the file where the creator application fills in a creation date. This is often the case for photos, where the camera will fill in some Exif data in JPEG or TIFF images, including the creation time. Nikon's NEF image format wraps around TIFF and supports Exif as well.

There are ready-made tools to rename image files containing Exif data to include the creation date in the file name. renaming images to include creation date in name shows two solutions, with exiftool and exiv2.

I don't think either tool lets you include a counter in the file name. You can do your renaming in two passes: first include the date (with as high resolution as possible to retain the order) in the file name, then number the files according to that date part (and chuck away the time). Since modern DSLRs can fire bursts of images (Nikon's D4s shoots at 11fps) it is advisable to retain the original filename as well in the first phase, as otherwise it would potentially lead to several files with the same file name.

exiv2 mv -r %Y%m%d-%H%M%S:basename: *.NEF
# exiv2 uses `strftime(3)`, so `%Y%m%d-%H%M%S` returns YYYYMMDD-hhmmss
# :basename: is a naming variable exiv2's `-r`-handle provides. See `exiv2 -h` for more  
# Now you have files with names like 20140630-235958_DSCC1234.NEF.
# Note that chronological order and lexicographic order agree with this naming format.
i=10000
for x in *.NEF; do
  i=$((i+1))
  mv "$x" "${x%-*}_FOO_${i#1}.NEF"
done

${x%-*} removes the part after the - character. The counter variable i counts from 10000 and is used with the leading 1 digit stripped; this is a trick to get the leading zeroes so that all counter values have the same number.

Rename files by incrementing a number within the filename has other solutions for renaming a bunch of files to include a counter.

If you want to use a file's timestamp rather than Exif data, see Renaming a bunch of files with date modified timestamp at the end of the filename?


As a general note, don't generate shell code and then pipe it into a shell. It's needlessly convoluted. For example, instead of

find -name '*.NEF' | 
gawk 'BEGIN{ a=1 }{ printf "mv %s %04d.NEF\n", $0, a++ }' | 
bash

you can write

find -name '*.NEF' | 
gawk 'BEGIN{ a=1 }{ system(sprintf("mv %s %04d.NEF\n", $0, a++)) }'

Note that both versions could lead to catastrophic results if a file name contained shell special characters (such as spaces, ', $, `, etc.) since the file name is interpreted as shell code. There are ways to turn this into robust code, but this isn't the easiest approach, so I won't pursue that approach.


¹ Note that there is something called the “ctime”, but the c isn't for creation, it's for change. The ctime changes every time anything changes about the file, either in its content or in its metadata (name, permissions, …). The ctime is pretty much the antithesis of a creation time.

Related Question