1

I was unable to comment on this old post,

How can I rename photos, given the EXIF data?

but it had a comment that seemed exactly what I needed so I started a new post, I hope not bad form.

I get what the code does, the piping from jhead through grep and SED...

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

I want a very similar outcome but I'd like to grunge the date a bit, ie. rename/output of: yyyymmdd-hhmmss but I can't figure out (translate) the SED regex. I tried

sed 's/^File date[^:]\+: \(\d{4}\):\(\d\d\):\(\d\d\) \(\d\d\):\(\d\d\):\(\d\d\)$/\1\2\3-\4\5\6/'

but all I get is

File date   :  yyyy:mm:yy hh:mm:ss

I would very much like to understand SED and the regex's that make it work, I realise it is quite powerful for stream editing. I particularly do not understand this part [^:] I think that sometimes the caret signifies BOL just as $ is EOL ... But, as I try to post this and I see other SED posts I learn (again?) that "^" is perhaps meaning "not" so [^:] means "match NOT :" .. ? But then just after that IS a :

anyway, lost I am.

Dee
  • 33
  • 4
  • Can you tell us what OS you're using? For example, on my vintage of MacOS filenames with : colons are disallowed--copy/paste into the save dialog box results in automatic conversion of : colons into - hyphens. – jubilatious1 Oct 20 '21 at 04:28
  • 1
    @jubilatious1 Linux Mint Uma 20.2 Dell OptiPlex 7080 32gb ram. I should make a signature of that to help - more foolishness from me. I used to run a Mac myself - quite enjoyed the BSD underneath. It's the price tag that pushed me off them, perhaps a little the privacy thing now... =/ – Dee Oct 21 '21 at 03:02

5 Answers5

3

AFAICT, it looks like you want to change the space in the date string to a - character?

That's actually pretty easy....but, first, there are some problems with that shell script. Firstly, it uses backticks for command substitution instead of $( and ). Secondly, it uses grep unnecessarily (sed can do what grep's doing here). Thirdly, the regex search and replace you want to do is much easier to do in two steps than one (first remove the "File date" part of the line, then modify the remaining date string).

Here's an improved version of that script:

for i in *.JPG; do
  j=$(jhead "$i" | sed -n -E '/^File date/ { s/^File date +: +//; s/ /-/p}').jpg
  echo mv -i "$i" "$j"
done

In English: the output of the jhead command is piped into sed. The sed option -n tell sed not to output any lines unless told to by a p (print) statement. The -E option tells it to use Extended Regular Expressions (ERE) instead of Basic Regular Expressions (BRE)...this is mostly so I can use the + modifier to match one-or-more spaces.

The sed script itself first checks if the line begins with "File date". If it does, it executes a block of sed commands (i.e. two or more commands wrapped in { and }).

The first command in the block removes "File date" followed by one-or-more space, a colon, and then one-or-more spaces from the line. This will result in the line containing just the date.

The second command changes the first (and only) space character with a dash -. There is a p at the end of this s/// command, which tells sed to print the modified line.

Example run:

$ ls -l *.JPG
-rw-r--r-- 1 cas cas 1110176 Oct 20 14:04 abc.JPG
-rw-r--r-- 1 cas cas 1132711 Oct 20 14:04 def.JPG
-rw-r--r-- 1 cas cas 1061121 Oct 20 14:04 ghi.JPG

$ for i in *.JPG; do j="$(jhead "$i" |sed -n -E '/^File date/ { s/^File date +: +//; s/ /-/p }').jpg" echo mv -i "$i" "$j" done mv -i abc.JPG 2021:10:20-14:04:08.jpg mv -i def.JPG 2021:10:20-14:04:09.jpg mv -i ghi.JPG 2021:10:20-14:04:10.jpg

BTW, this script is not particularly safe. It doesn't, for example, take into account the fact that two different jpeg files can have exactly the same timestamp.

Something like the following is far from perfect, but would be safer/better:

for i in *.JPG; do
  j="$(jhead "$i" | sed -n -E '/^File date/ { s/^File date +: +//; s/ /-/p }')"

c=1 while [ -e "$j-$c.jpg" ] ; do let c+=1 done j="$j-$c"

mv -iv "$i" "$j.jpg" done

(note: this version actually renames files, it doesn't just echo what it would do. This was necessary for testing because if it didn't do that, it wouldn't know when to increment the counter variable, $c)

Example output:

renamed 'abc.JPG' -> '2021:10:20-14:04:08-1.jpg'
renamed 'def.JPG' -> '2021:10:20-14:04:08-2.jpg'
renamed 'ghi.JPG' -> '2021:10:20-14:04:08-3.jpg'

BTW, if you're likely to have more than 9 jpeg files with the same timestamp, you can use printf to make sure the counter is two or three digits and zero-padded. e.g.

for i in *.JPG; do
  j="$(jhead "$i" | sed -n -E '/^File date/ { s/^File date +: +//; s/ /-/p }')"

c=1 while [ -e "$(printf "%s-%03i.jpg" "$j" "$c")" ] ; do let c+=1 done j="$(printf "%s-%03i.jpg" "$j" "$c")"

mv -iv "$i" "$j" done

renamed 'abc.JPG' -> '2021:10:20-14:04:08-001.jpg' renamed 'def.JPG' -> '2021:10:20-14:04:08-002.jpg' renamed 'ghi.JPG' -> '2021:10:20-14:04:08-003.jpg'


RE: your questions about the caret ^ character.

Outside of a bracket expression, it's a beginning-of-line anchor - e.g. ^File date matches "File date" only at the start of a line.

Inside a bracket expression, it negates/inverts the meaning of the expression. e.g. where [A-Z] matches all characters from A to Z, [^A-Z] matches all characters which are not between A and Z.

cas
  • 78,579
3

Why reinvent the wheel?

jhead has an option to do exactly what you want.

-n[format_string] This option causes files to be renamed and/ or mmoved [sic!] using the date information from the Exif header "DateTimeOriginal" field.

Even multiple photos with the same time stamp are considered:

If the target name already exists, the name will be appended with "a", "b", "c", etc, unless the name ends with a letter, in which case it will be appended with "0", "1", "2", etc.

Example:

jhead -n%Y%m%d-%H%M%S *.JPG
pLumo
  • 22,565
1

Thank you all so much. I worry about asking dumb questions but I wish to learn so I plop them right out there. Which is why, even though this may have been reinventing the wheel, I did learn something. Much as Jhead might have done it all itself, thank you @pLumo , it was the machinations of SED and (forgetting) regex that had me piqued. I did see, on more thought, that the [^:]+ was search for one or more characters that are NOT ":" (thank you @cas) sadly I was too slow to grok that before. And I also was not aware of SED's syntax of using [[:digit:]]. So the line I was trying to build looks like this

j=`jhead "$i" | grep date | sed 's/^File date[^:]\+: \([[:digit:]]\{4\}\):\([[:digit:]]\{2\}\):\([[:digit:]]\{2\}\) \([[:digit:]]\{2\}\):\([[:digit:]]\{2\}\):\([[:digit:]]\{2\}\)$/\1\2\3-\4\5\6/'`.jpg

@cas in this instance yes I was changing to spaces but also removing the ":"s. By learning how to get SED to match the digits allows me to configure any format/pattern I like rather than just substitute out the delimiters.

My biggest mental block seemed to come from having so many operands being escaped (I hope that's the correct terminology) ie. the backslash before .. everything ! I just couldn't grok it like that.

All your suggestions have given me more coding tools/techniques, I endeavor to make notes so that I can find them before asking more newbie questions!. Big Thanks

We had a lightning storm just after I posted so even though I found my way through I was unable to post a reply sooner - I have to disconnect my desktop and can just do some browsing using my phone, not much for trying to write an essay on the mobile though.

Dee
  • 33
  • 4
0

Using Raku (formerly known as Perl_6)

for i in *.JPG; do

j="$(exiftool "$i" |
raku -e 'for lines.grep(/"File Modification Date/Time"/).split(":", 2).map(*.trim) -> $k,$v { put( $v.subst(":", "-", :x(1..2)).subst(" ", "T").DateTime ~ ".JPG") };' )";

echo cp -iv "$i" "$j"; done

Sample Input:

footer_divider_test1.JPG footer_divider_test2.JPG

Sample Output (removing echo):

overwrite 2021-10-21T21:01:31-07:00.JPG? (y/n [n]) y
footer_divider_test1.JPG -> 2021-10-21T21:01:31-07:00.JPG
overwrite 2021-10-21T21:01:31-07:00.JPG? (y/n [n]) y
footer_divider_test2.JPG -> 2021-10-21T21:01:31-07:00.JPG

Here's an approach using Raku, the language formerly known as Perl6--released as Perl6 in 2015; renamed Raku in 2019. Hoping for a challenge, the input .JPG file(s) I'm starting with are graphics that actually have three rows of Date/Time information (data is extracted from the line File Modification Date/Time, above). The above code starts with exiftool output (sorry no jhead here). And because no iterator is included, the caveats mentioned by @cas still apply.

The exiftool output is piped into Raku, which implements a classic idiom: lines containing the string-literal ("File Modification Date/Time") are grep-ped out, these lines are split on : colon into a maximum of 2 elements, and each element is trim-med to remove whitespace.

Element pairs are assigned to variables $k,$v, and the second ($v) containing the date/time is passed through two subst() routines. These routines A). convert the :x(1..2) first-and-second (date) : colons into - hyphens, and B). convert a " " blank space into T. The reason to do these last two tweaks is because afterwards, Raku's .DateTime routine recognizes the input as a valid ISO 8601 timestamp.

Why do all this work as compared to sed? Well, the benefits of using a valid ISO 8601 timestamp are huge because Raku provides built-in support for these objects (no external libraries necessary). The most obvious result (first of all) is that \([[:digit:]]\{4\}\):\([[:digit:]]\{2\}\):\([[:digit:]]\{2\}\) grunging is eliminated.

Second of all, now you have access to Raku's built-in DateTime routines, such as:

  • method year, method month, method day, method formatter, method is-leap-year, method day-of-month, method day-of-week, method day-of-year, method days-in-month, method week, method week-number, method week-year, method weekday-of-month, method yyyy-mm-dd, method mm-dd-yyyy, method dd-mm-yyyy, method daycount, method IO, method earlier, method later.

So, lets say you want only the date in YYYY-MM-DD format (ISO 8601). That's easy: just insert the .yyyy-mm-dd routine in the method chain after .DateTime. Or how about the day-of-year? just insert the .day-of-year routine in the method chain after .DateTime. (Or a custom date format? Use the date .formatter to design your own). Short example:

STD (ISO 8601):

~$ exiftool footer_divider_test1.JPG | raku -e 'for lines.grep(/"Date/Time"/).map(*.split(":", 2 )) { for .map(*.trim) -> $k,$v { say($k => $v.values.subst(":", "-", :x(1..2)).subst(" ", "T").DateTime ) }; };'
File Modification Date/Time => 2021-10-21T21:01:31-07:00
File Access Date/Time => 2021-10-23T09:38:33-07:00
File Inode Change Date/Time => 2021-10-22T23:10:27-07:00

YYYY-MM-DD (ISO 8601):

~$ exiftool footer_divider_test1.JPG | raku -e 'for lines.grep(/"Date/Time"/).map(*.split(":", 2 )) { for .map(*.trim) -> $k,$v { say($k => $v.values.subst(":", "-", :x(1..2)).subst(" ", "T").DateTime.yyyy-mm-dd ) }; };'
File Modification Date/Time => 2021-10-21
File Access Date/Time => 2021-10-23
File Inode Change Date/Time => 2021-10-22

Finally, while some programmers may prefer shell scripting, Raku does provide a dir() routine for pulling out file names, as well as a rename() routine for renaming files and directories. These functions may be useful to implement a script on platforms where classic shell-mediated file-globbing is limited and/or absent (see SO link below).

https://en.wikipedia.org/wiki/ISO_8601
https://docs.raku.org/language/temporal
https://docs.raku.org/type/Dateish
https://stackoverflow.com/questions/69384754/how-do-i-use-raku-e-and-n-with-multiple-file-glob/69400187
https://raku.org

jubilatious1
  • 3,195
  • 8
  • 17
0

Please, use EXIV2 CLI tool like this:

exiv2 -r "IMG_%Y%m%d_%H%M%S" -F *

if your current directory contains image files.

It can do a lot of things by querying image EXIF metadata. In particular, with -r can rename files accordingly.

Visit the linked page for full details and its man-page for full set of options.

EnzoR
  • 933