5

I'm trying to get the 2-digit month and 2-digit year a file was modified, but's it's not working..

modified=$(stat -c %y "$line"); 
# modified="2018-08-22 14:39:36.400469308 -0400"
if [[ $modified =~ ".{2}(\d{2})-(\d{2})" ]]; then
    echo ${BASH_REMATCH[0]}
    echo ${BASH_REMATCH[1]
fi

What am I doing wrong?

  • what's not working about it? If this question matches exactly what you're trying in bash, the issue is you're missing a closing brace } at the end of the second-to-last line of your script. – Kevin Kruse Aug 27 '18 at 21:09
  • FYI, you could use the date command to convert a stat output of a UNIX timestamp into the exact date portion you want. – Wildcard Aug 27 '18 at 21:11
  • this question appears to be a request for how to implement a [ . -nt .[.] ] test. – mikeserv Aug 28 '18 at 23:04

3 Answers3

7

First, the quotes suppress the meaning of the special characters in the regex (online manual):

An additional binary operator, =~, is available, ... Any part of the pattern may be quoted to force the quoted portion to be matched as a string. ... If you want to match a character that’s special to the regular expression grammar, it has to be quoted to remove its special meaning.

The manual goes on to recommend putting the regex in a variable to prevent some clashes between the shell parsing and the regex syntax.

Second, \d doesn't do what you think it does, but just matches a literal d.

Also note that ${BASH_REMATCH[0]} contains the whole matching string, and indexes 1 and up contain the captured groups.

I'd also strongly suggest using four-digit years, so:

modified=$(stat -c %y "$file")
re='^([0-9]{4})-([0-9]{2})'
if [[ $modified =~ $re ]]; then
    echo "year:  ${BASH_REMATCH[1]}"
    echo "month: ${BASH_REMATCH[2]}"
else
    echo "invalid timestamp"
fi

For a file modified today, that gives year: 2018 and month: 08. Note that numbers with a leading zero will be considered octal by the shell and possibly other utilities.

(Four-digit years have less issues if you ever need to handles dates from the 1900's, and they're easier to recognize as years and not days of month.)

ilkkachu
  • 138,973
  • Note that changed in 3.2, you can also use shopt -s compat31 or BASH_COMPAT=3.1 for [[ $modified =~ ".{2}([0-9]{2})-([0-9]{2})" ]] to work. – Stéphane Chazelas Aug 27 '18 at 21:39
  • pretty bold suggesting that I use data other than what I said I needed without having even the slightest clue what I needed it for, but other than that this makes a lot of sense. thank you for your time. I'll mark it as the answer as soon as I have a chance to try it out in the morning. – I wrestled a bear once. Aug 28 '18 at 01:18
4

Don't need regular expressions for this:

$ touch -t 197001010000 myfile
$ ls -l myfile
-rw-rw-r-- 1 jackman jackman 0 Jan  1  1970 myfile
$ IFS='-' read -r year month _rest < <(stat -c %y myfile)
$ echo "$year:${year#??}"$month"
1970:70:01
glenn jackman
  • 85,964
3

As an alternative, with GNU date, you can do:

eval "$(date -r "$file" +'year=%Y month=%-m day=%-d')"

To have the modification time's year, month and day component stored in $year, $month and $day respectively (as decimal integers, remove the -s in %-m and %-d if you care for the leading zeros; see also %y for 2-digit years).

(note that contrary to GNU stat, for files of type symlink, the modification time of the target of the symlink is considered rather than that of the symlink itself. With GNU stat, you'd use stat -L).