Extract date substring from file name

Viewed 187

I'm trying to extract the date from a string/file path in bash. Here's what I would hope to work but isn't:

#!/bin/bash

f=/mnt/media/CameraUploads/CMGPH_20190626_200707386.gif

if [[ $f =~ (19|20)\d\d(0[1-9]|1[012])(0[1-9]|[12][0-9]|3[01]) ]]; then
    strresult=${BASH_REMATCH[1]}
    echo $strresult
else
    echo "unable to parse string $f"
fi

I'm expecting $strresult = 20190626

What am I doing wrong?

2 Answers

The easiest way is simply to use a parameter expansion with substring removal to isolate the date and then pass to date -d to convert to a date in any format you like, e.g.

f=/mnt/media/CameraUploads/CMGPH_20190626_200707386.gif
t="${f%_*}"     ## trim from right to 1st '_'
t="${t##*_}"    ## trim from left to final '_' isolating date
date -d "$t"    ## call date format as needed

Example Use/Output

$ f=/mnt/media/CameraUploads/CMGPH_20190626_200707386.gif
> t="${f%_*}"     ## trim from right to 1st '_'
> t="${t##*_}"    ## trim from left to final '_' isolating date
> date -d "$t"    ## call date format as needed
Wed Jun 26 00:00:00 CDT 2019

You can do the same thing with the time portion of the filename if you wish to include that.

To isolate the full date/time string you could do:

f=/mnt/media/CameraUploads/CMGPH_20190626_200707386.gif
t="${f%.*}"     ## trim from right to 1st '.'
t="${t##*_}"    ## trim from left to final '_' isolating time
t="${t:0:2}:${t:2:2}:${t:4:2}.${t:6:3}"     ## format time with : between 
d="${f%_*}"     ## trim from right to 1st '_'
d="${d##*_}"    ## trim from left to final '_' isolating date
d="${d:0:4}-${d:4:2}-${d:6:2}"              ## format date with - between
date -d "$d $t" ## call date format as needed

The final "$d $t" string passed to date is:

2019-06-26 20:07:07.386

Example Output

Wed Jun 26 20:07:07 CDT 2019

Edit -- Date/Time Anywhere In Filename

If, per your edit, the date_time can appear anywhere in the filename, and if the time may or may not include milliseconds, the efficient way to handle the date/time extraction is to use sed to isolate the yyyymmdd_hhmmssSSSS string and then using process substitution feed the isolated string to a while loop for processing as above. (the only change being that you check whether there are milliseconds before adding the .SSSS milliseconds to the time string -- limited to 4 digits in the example below -- add as necessary)

#!/bin/bash

while read line || [ -n "$line" ]; do
    d="${line%_*}"
    d="${d##*_}"    ## trim from left to final '_' isolating date
    d="${d:0:4}-${d:4:2}-${d:6:2}"          ## format date with - between
    t="${line#*_}"
    t="${t##*_}"    ## trim from left to final '_' isolating time
    t="${t:0:2}:${t:2:2}:${t:4:2}"          ## format time with : between
    [ -n "${t:6:4}" ] && t="$t.${t:6:4}"    ## append miliseconds if present
    printf "%s\n\n" "$(date -d "$d $t")"
done < <(sed 's/^[^0-9]*\([0-9][0-9_]*\).*$/\1/' "$1")

Example Input Filenames

$ cat file
20181214_195948-ANIMATION.gif
20191012_223451.jpg
IMG_20181122_182138511.jpg
VID_20160909_163547.3gp

Example Use/Output

$ bash extract.sh file
Fri Dec 14 19:59:48 CST 2018

Sat Oct 12 22:34:51 CDT 2019

Thu Nov 22 18:21:38 CST 2018

Fri Sep  9 16:35:47 CDT 2016

That should cover the filenames posted in the comment.

Bash regex does not support \d expression which is not POSIX standard.
Instead you need to use [0-9] or [[:digit:]].

Please modify your regex lines as:

if [[ $f =~ (19|20)[0-9]{2}(0[1-9]|1[012])(0[1-9]|[12][0-9]|3[01]) ]]; then
    strresult=${BASH_REMATCH[0]}

Then you'll get:

20190626

Note that ${BASH_REMATCH[0]} holds the substring which matches the entire regex while ${BASH_REMATCH[1]} holds the portion which matches the 1st parenthesized subexpression.

Hope this helps.

Related