12

I have a files named with YYYYMMDD in the file name, such as

file-name-20151002.txt

I want to determine if this file was modified after 2015-10-02.

Notes:

  • I can do this by looking at the output of ls, but I know that parsing the output of ls is a bad idea.
  • I don't need to find all files dated after a specific date, just need to test one specific file at a time.
  • I am not concerned about the file being modified on the same date after I created it. That is, I just want to know if this file with 20151002 in the name was modified on Oct 03, 2015 or later.
  • I am on MacOs 10.9.5.
1
  • Apologies for not including the OS earlier. Oct 29, 2015 at 3:36

5 Answers 5

9

The following script will check the dates of all files specified on the command line:

It requires GNU versions of sed, date, and stat

$ cat check-dates.sh 
#! /bin/bash

for f in "$@" ; do
  # get date portion of filename
  fdate=$(basename "$f" .txt | sed -re 's/^.*(2015)/\1/')

  # convert it to seconds since epoch + 1 day
  fsecs=$(echo $(date +%s -d "$fdate") + 86400 | bc )

  # get modified timestamp of file in secs since epoch
  ftimestamp=$(stat -c %Y "$f")

  [ "$ftimestamp" -gt "$fsecs" ] && echo "$f has been modified after $fdate"
done

$ ./check-dates.sh file-name-20151002.txt 
file-name-20151002.txt has been modified after 20151002
$ ls -l file-name-20151002.txt 
-rw-rw-r-- 1 cas cas 0 Oct 26 19:21 file-name-20151002.txt

Here's a version that is untested but should work on Macs (and on FreeBSD etc) if I've read the on-line man pages correctly:

#! /bin/bash

for f in "$@" ; do
  # get date portion of filename
  fdate=$(basename "$f" .txt | sed -e 's/^.*\(2015\)/\1/')

  # convert it to seconds since epoch + 1 day
  fsecs=$(echo $(date -j -f %Y%m%d "$fdate" +%s) + 86400 | bc )

  # get modified timestamp of file in secs since epoch
  ftimestamp=$(stat -f %m "$f")

  [ "$ftimestamp" -gt "$fsecs" ] && echo "$f has been modified after $fdate"
done
13
  • An even better solution would be one not requiring 4-5 extensions vs POSIX. Oct 26, 2015 at 8:47
  • 2
    Feel free to write your own version, then. I write what I want to write, not what you want me to write....and that includes using GNU versions of tools. IMO, GNU IS the de-facto standard. and the relative numbers of linux distros in use vs non-linux, non-gnu *nixes supports that.
    – cas
    Oct 26, 2015 at 8:53
  • 6
    @cas: a rather harsh reply to Thomas's comment which was, I believe, just a suggestion to improve your solution... In the real world, having "the most standard possible" script is not only a bonus, but is necessary (or rather, mandatory). Many places can't install the GNU version of tools (I happen to know 3 different places where some system's latest vers of bash are 2.x and awk is so old it is very, very close to the original one). This question's needs is probably not "critical" (but could be) and also not often seeked/used by other people, but in general, having a portable solution is a + Oct 26, 2015 at 12:07
  • I am getting sed: illegal option -- r with MacOS 10.9.5. Oct 29, 2015 at 3:36
  • -r is a GNU sed option to tell it to use extended regular expressions. You could change the sed script to use basic regexp rather than extended (e.g.sed -e 's/^.*\(2015\)/\1/') but the rest of the script will probably still fail because it requires the GNU versions of date and stat, and I don't think Macs come with them as standard (although they are available for Mac in pre-compiled packages)
    – cas
    Oct 29, 2015 at 3:41
5

Here are some possible ways with :

  • OSX stat:

    newer () {
    tstamp=${1:${#1}-12:8}
    mtime=$(stat -f "%Sm" -t "%Y%m%d" "$1")
    [[ ${mtime} -le ${tstamp} ]] && printf '%s\n' "$1 : NO: mtime is ${mtime}" || printf '%s\n' "$1 : YES: mtime is ${mtime}"
    }
    
  • GNU date:

    newer () {
    tstamp=${1:${#1}-12:8}
    mtime=$(date '+%Y%m%d' -r "$1")
    [[ ${mtime} -le ${tstamp} ]] && printf '%s\n' "$1 : NO: mtime is ${mtime}" || printf '%s\n' "$1 : YES: mtime is ${mtime}"
    }
    
  • zsh only:

    zmodload zsh/stat
    newer () {
    tstamp=${1:${#1}-12:8}
    mtime=$(zstat -F '%Y%m%d' +mtime -- $1)
    [[ ${mtime} -le ${tstamp} ]] && printf '%s\n' "$1 : NO: mtime is ${mtime}" || printf '%s\n' "$1 : YES: mtime is ${mtime}"
    }
    

Usage:

newer FILE

Example output:

file-name-20150909.txt : YES: mtime is 20151026

or

file-name-20151126.txt : NO: mtime is 20151026
0
4

Using bash and stat and expr to get the dates as numbers and comparing them:

#!/bin/bash
for file
do  moddate=$(stat -f %m -t %F "$file") # MacOS stat
    moddate=${moddate//-/} # 20151026
    if filedate=$(expr "$file" : '.*-\([0-9]*\).txt')
    then  if [ $moddate -gt $filedate ]
          then echo "$file: modified $moddate"
          fi
    fi
done

This was my earlier Linux-specific answer.

#!/bin/bash
for file
do  moddate=$(stat -c %y "$file")
    moddate=${moddate%% *} # 2015-10-26
    moddate=${moddate//-/} # 20151026
    if [[ "$file" =~ ([0-9]{8}).txt$ ]]
    then  if [[ $moddate > ${BASH_REMATCH[1]} ]]
          then echo "$file: modified $moddate"
          fi
    fi
done

The bash =~ regexp operator captures the 8 digits of the filename into bash array BASH_REMATCH. [[ ]] compares the strings, though you simply compare them as numbers instead in [ -gt ].

5
  • MacOS 10.9.5 does not seem to have the -c option to stat. Oct 29, 2015 at 3:33
  • My bad. The equivalent seems to be stat -f %m -t %F "$file". Sorry, I can't test it.
    – meuh
    Oct 29, 2015 at 7:14
  • With the change in the stat as per your comment, things seem to execute but no output for all test cases. But, what is the "$file" =~ ([0-9]{8}).txt$ doing? Oct 29, 2015 at 7:46
  • It looks for the 8 digits followed by .txt at the end of the filename. The parentheses () captures the 8 digits part, and you can find it in ${BASH_REMATCH[1]}.
    – meuh
    Oct 29, 2015 at 7:58
  • If your bash doesnt work with if [[=~]] you can try the basic if filedate=$(expr "$file" : '.*-\([0-9]*\).txt') and then replace ${BASH_REMATCH[1]} by $filedate.
    – meuh
    Oct 29, 2015 at 8:08
4

You could do this:

  • extract the year/month/day values into a shell variable,
  • create a temporary file
  • use the touch command (adding 0s for hour/minute/second) to set the modification date for the temporary file

Because your question is about bash, you likely are using Linux. The test program used in Linux (part of coreutils) has extensions for timestamp comparison (-nt and -ot) not found in POSIX test.

POSIX comments about this in the rationale for test:

Some additional primaries newly invented or from the KornShell appeared in an early proposal as part of the conditional command ([[]]): s1 > s2, s1 < s2, str = pattern, str != pattern, f1 -nt f2, f1 -ot f2, and f1 -ef f2. They were not carried forward into the test utility when the conditional command was removed from the shell because they have not been included in the test utility built into historical implementations of the sh utility.

Using that extension, you could then

  • create another temporary file with the date you want to compare against
  • use the -nt operator in test to make the comparison you asked for.

Here is an example. A clarification by OP mentioned the platform, so one could use stat as an alternative to temporary files (compare OSX and Linux):

#!/bin/bash
# inspect each file...

with_tempfile() {
    echo "** with temporary file $name"
    test=$MYTEMP/$name
    touch -t $date $test
    [ $name -nt $test ] && echo "...newer"
}

# alternatively (and this is system-dependent)
with_stat() {
    echo "** with stat command $name"
    stat=$(stat -t "%Y%m%d%H%M" -f "%Sm" $name)
    [ $stat -gt $date ] && echo "...newer"
}

MYTEMP=$(mktemp -d /var/tmp/isnewer.XXXXXX)
trap "rm -rf $MYTEMP" EXIT
for name in file-name-[0-9][0-9]*.txt
do
    if [ -f "$name" ];
    then
            date=${name%%.txt}
            date=${date/file-name-}
            date=${date//-/}2359
            with_tempfile $name
            with_stat $name
    fi
done
3
  • When creating temporary files, it is standard practice to remove overlooked files with a trap command. Oct 26, 2015 at 20:44
  • I am on MacOS 10.9.5. Oct 29, 2015 at 3:36
  • Thanks for the clarification. Temporary files aren't as elegant as some possible approaches relying on additional extensions, but I'll provide a short sample script, for consistency. Oct 29, 2015 at 8:17
1

Another zsh approach:

zmodload zsh/stat # best in ~/.zshrc or
zmodload -F zsh/stat +b:zstat # to avoid overriding an eventual
                              # system stat command.
setopt extendedglob # best in ~/.zshrc

ls -ld -- **/*[0-9](#c8)*(De@'zstat -F %Y%m%d -A m +mtime $REPLY &&
  [[ ${(SM)${REPLY:t}#[0-9](#c8)} != $m ]]'@)

Would report the files that have something that looks like a timestamp in their name but which does not correspond to their last modification time.

zsh has a plethora of operators like that to manipulate globs and variables. It's very handy to get any job done quickly (and generally reliably) but it's quite hard to get your head around them all and it often produces what we usually call write-only code (euphemism for illegible code, though also implies that you don't need to read it as you write it for a single usage at the command prompt)

A quick run through:

  • **/pattern(qualifier): recursive glob with glob qualifier.
  • [0-9](#c8) matches 8 digits. That's the zsh extendedglob equivalent of ksh's {8}([0-9]).
  • D: include hidden files
  • e@...@: the eval glob qualifier to run some text to further qualify files. File path passed in $REPLY
  • zstat... retrieve the mtime formatted as YYYYMMDD in the $m array.
  • ${REPLY:t}: expands to the ttail (the basename) of the file.
  • ${(SM)something#pattern}. Extract the part matching pattern from something. Those S (Substring search) and M (expand to Matched portion) are called parameter expansion flags.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .