[alt.sources] how to compare file modification time

chip@chinacat.Unicom.COM (Chip Rosenthal) (07/31/90)

Attached at the bottom is a command-line interface to the stat() system
call.  To do the file compare, you could do:

    time1=`stat -M $file1`
    time2=`stat -M $file2`
    if [ $time1 -gt $time2 ] ; then
	echo "$file1 is newer than $file2"
    else
	echo "$file2 is newer than $file1" # well...could be same time...
    fi

In article <1990Jul27.153223.12416@chinet.chi.il.us>
    les@chinet.chi.il.us (Leslie Mikesell) writes:
>Also, is there a handy way to set the timestamp on a directory to that
>of the newest file below it?

Assuming you've got a "settime" command, this would work:

    DIR=/tmp/foo
    settime -f `stat -M $DIR/* | sort -rn +1 | sed -e 's/:.*//' -e q` $DIR

This sets the directory's modified time to that of the newest file in
that directory.  A little more complexity is needed if you want to find
the newest file in the entire directory tree.

The stat program has been tested on SCO XENIX 386.  I wouldn't be surprised
if there are some system dependancies in the include files or data types
of the stat structure elements, but it should be very simple to fix up.
(The whole thing is driven by a single table.)

#! /bin/sh
# this is a "shar" archive - run through "/bin/sh" to extract 4 files:
#   README stat.c stat.man Makefile
# Wrapped by bin@chinacat on Sun Jul 29 20:19:14 CDT 1990
# Unpacking this archive requires:  sed test wc (possibly mkdir)
# Existing files will not be clobbered unless "-c" is specified on the cmd line.
if test -f README -a "$1" != "-c" ; then
    echo "README: file exists - will not be overwritten"
else
    echo "x - README (file 1 of 4, 586 chars)"
    sed -e 's/^X//' << 'END_OF_FILE_README' > README
Xstat displays information on a file - the same information available
Xthrough the stat() system call.  For example, to see all the information
Xon /etc/termcap you can do:
X
X    % stat -x /etc/termcap
X    DEV=1,40
X    INODE=3087
X    MODE=644
X    NLINK=4
X    UID=bin
X    GID=bin
X    RDEV=103,181
X    SIZE=94032
X    ATIME=Sun Jul 29 18:36:18 1990
X    MTIME=Sun Apr 30 22:53:31 1989
X    CTIME=Sun Nov  5 02:23:55 1989
X
XThe "-x" option returns all available things.  You can request individual
Xitems, for example:
X
X    % stat -u /etc/termcap
X    bin
X
XChip Rosenthal
X<chip@chinacat.Unicom.COM>
END_OF_FILE_README
    size="`wc -c < README`"
    if test 586 -ne "$size" ; then
	echo "README: extraction error - got $size chars"
    fi
fi
if test -f stat.c -a "$1" != "-c" ; then
    echo "stat.c: file exists - will not be overwritten"
else
    echo "x - stat.c (file 2 of 4, 7223 chars)"
    sed -e 's/^X//' << 'END_OF_FILE_stat.c' > stat.c
X/*
X * stat - User-level interface to file information.
X *
X * This program is table driven through the "Stat_tab[]" list, which contains
X * all of the items we can display.  Each table entry specifies the command
X * line option which selects it, a pointer to pick the appropriate value out
X * of a stat structure, and a pointer to the procedure to format and print the
X * information.  Hacking this program should just be a matter of adding table
X * entries, and possibly new formatting procedures.
X *
X * Sun Jul 29 20:16:50 1990 - Chip Rosenthal <chip@chinacat.Unicom.COM>
X *	Commented for posting to alt.sources.
X */
X
X#include <stdio.h>
X#include <sys/types.h>
X#include <sys/stat.h>
X#include <sys/sysmacros.h>
X#include <pwd.h>
X#include <grp.h>
X
X/*
X * Used to mark what kind of data item we are looking at.
X */
X#define TYPE_DEV_T	1
X#define TYPE_INO_T	2
X#define TYPE_OFF_T	3
X#define TYPE_SHORT	4
X#define TYPE_TIME_T	5
X#define TYPE_USHORT	6
X
X/*
X * The following procedures take a "long" value and format it as required.
X */
Xvoid pr_decvalue();	/* print as a decimal value		*/
Xvoid pr_octvalue();	/* print as an octal value		*/
Xvoid pr_perms();	/* strip and print permissions in octal	*/
Xvoid pr_device();	/* print device number as "major,minor"	*/
Xvoid pr_user();		/* lookup and print as a user name	*/
Xvoid pr_group();	/* lookup and print as a group name	*/
Xvoid pr_time();		/* print as a formatted time string	*/
Xvoid pr_days();		/* print as number of days ago		*/
X
X/*
X * The file status will be loaded into this buffer.
X */
Xstruct stat Stat_buf;
X
X/*
X * Table of things we can do.
X */
Xstruct stat_desc {
X    int sd_enabled;	/* will be set nonzero to do this entry		*/
X    char *sd_optlist;	/* list of cmd line options which enable this	*/
X    char *sd_descrip;	/* text for item label				*/
X    void *sd_ptr;	/* pointer to item datum within "Stat_buf"	*/
X    int sd_datatype;	/* indicates what "sd_ptr" points to		*/
X    void (*sd_func)();	/* procedure to format and print the item	*/
X} Stat_tab[] = {
X    /*** lower case options ************************************************/
X    { 0, "dx", "DEV",   (void*)&Stat_buf.st_dev,   TYPE_DEV_T,  pr_device   },
X    { 0, "ix", "INODE", (void*)&Stat_buf.st_ino,   TYPE_INO_T,  pr_decvalue },
X    { 0, "px", "MODE",  (void*)&Stat_buf.st_mode,  TYPE_USHORT, pr_perms    },
X    { 0, "nx", "NLINK", (void*)&Stat_buf.st_nlink, TYPE_SHORT,  pr_decvalue },
X    { 0, "ux", "UID",   (void*)&Stat_buf.st_uid,   TYPE_USHORT, pr_user     },
X    { 0, "gx", "GID",   (void*)&Stat_buf.st_gid,   TYPE_USHORT, pr_group    },
X    { 0, "rx", "RDEV",  (void*)&Stat_buf.st_rdev,  TYPE_DEV_T,  pr_device   },
X    { 0, "sx", "SIZE",  (void*)&Stat_buf.st_size,  TYPE_OFF_T,  pr_decvalue },
X    { 0, "ax", "ATIME", (void*)&Stat_buf.st_atime, TYPE_TIME_T, pr_time     },
X    { 0, "mx", "MTIME", (void*)&Stat_buf.st_mtime, TYPE_TIME_T, pr_time     },
X    { 0, "cx", "CTIME", (void*)&Stat_buf.st_ctime, TYPE_TIME_T, pr_time     },
X    { 0, "f",  "MTIME", (void*)&Stat_buf.st_mtime, TYPE_TIME_T, pr_days     },
X    /*** upper case options ************************************************/
X    { 0, "DX", "DEV",   (void*)&Stat_buf.st_dev,   TYPE_DEV_T,  pr_decvalue },
X    { 0, "IX", "INODE", (void*)&Stat_buf.st_ino,   TYPE_INO_T,  pr_decvalue },
X    { 0, "PX", "MODE",  (void*)&Stat_buf.st_mode,  TYPE_USHORT, pr_octvalue },
X    { 0, "NX", "NLINK", (void*)&Stat_buf.st_nlink, TYPE_SHORT,  pr_decvalue },
X    { 0, "UX", "UID",   (void*)&Stat_buf.st_uid,   TYPE_USHORT, pr_decvalue },
X    { 0, "GX", "GID",   (void*)&Stat_buf.st_gid,   TYPE_USHORT, pr_decvalue },
X    { 0, "RX", "RDEV",  (void*)&Stat_buf.st_rdev,  TYPE_DEV_T,  pr_decvalue },
X    { 0, "SX", "SIZE",  (void*)&Stat_buf.st_size,  TYPE_OFF_T,  pr_decvalue },
X    { 0, "AX", "ATIME", (void*)&Stat_buf.st_atime, TYPE_TIME_T, pr_decvalue },
X    { 0, "MX", "MTIME", (void*)&Stat_buf.st_mtime, TYPE_TIME_T, pr_decvalue },
X    { 0, "CX", "CTIME", (void*)&Stat_buf.st_ctime, TYPE_TIME_T, pr_decvalue },
X    { 0, NULL, NULL,    (void*)0,                  0,           0           },
X};
X
Xmain(argc,argv)
Xint argc;
Xchar *argv[];
X{
X    char *s;
X    int print_titles, print_files, argi, ocount, i;
X    long value;
X    char *strchr();
X
X    /*
X     * Crack the command line options.
X     */
X    for ( argi = 1 ; argi < argc && *(s=argv[argi]) == '-' ; ++argi ) {
X	while ( *++s != '\0' ) {
X	    ocount = 0;
X	    for ( i = 0 ; Stat_tab[i].sd_optlist != NULL ; ++i ) {
X		if ( strchr(Stat_tab[i].sd_optlist,*s) != NULL ) {
X		    ++Stat_tab[i].sd_enabled;
X		    ++ocount;
X		}
X	    }
X	    if ( ocount == 0 ) {
X		fprintf(stderr,"%s: unknown option '%c'\n",argv[0],*s);
X		exit(1);
X	    }
X	}
X    }
X
X    /*
X     * If more than one item is printed, then titles should be shown.
X     * Note that if you double an option (i.e. "-dd") this will cause titles
X     * to be printed.  Consider it an undocumented feature...
X     */
X    switch ( ocount ) {
X	case 0:  fprintf(stderr,"%s: no options specified\n",argv[0]); exit(1);
X	case 1:  print_titles = 0; break;
X	default: print_titles = 1; break;
X    }
X
X    /*
X     * If more than one file is to be done, then names should be shown.
X     */
X    switch ( argc-argi ) {
X	case 0:  fprintf(stderr,"%s: no files specified\n",argv[0]); exit(1);
X	case 1:  print_files = 0; break;
X	default: print_files = 1; break;
X    }
X
X    /*
X     * Go through the remaining command line items and get their status.
X     */
X    for ( ; argi < argc ; ++argi ) {
X
X	if ( stat(argv[argi],&Stat_buf) != 0 ) {
X	    fprintf(stderr,"%s: could not get file status of '%s'\n",
X		argv[0],argv[argi]);
X	    continue;
X	}
X
X	for ( i = 0 ; Stat_tab[i].sd_optlist != NULL ; ++i ) {
X	    if ( !Stat_tab[i].sd_enabled )
X		continue;
X	    switch ( Stat_tab[i].sd_datatype ) {
X	    case TYPE_DEV_T:  value = *((dev_t *)Stat_tab[i].sd_ptr);  break;
X	    case TYPE_INO_T:  value = *((ino_t *)Stat_tab[i].sd_ptr);  break;
X	    case TYPE_OFF_T:  value = *((off_t *)Stat_tab[i].sd_ptr);  break;
X	    case TYPE_SHORT:  value = *((short *)Stat_tab[i].sd_ptr);  break;
X	    case TYPE_TIME_T: value = *((time_t *)Stat_tab[i].sd_ptr); break;
X	    case TYPE_USHORT: value = *((ushort *)Stat_tab[i].sd_ptr); break;
X	    }
X	    if ( print_files )
X		printf("%s: ", argv[argi]);
X	    if ( print_titles )
X		printf("%s=", Stat_tab[i].sd_descrip);
X	    (*Stat_tab[i].sd_func)(value);
X	}
X
X    }
X
X    exit(0);
X}
X
X
Xvoid pr_decvalue(value)
Xlong value;
X{
X    printf("%ld\n", value);
X}
X
Xvoid pr_octvalue(value)
Xlong value;
X{
X    printf("%lo\n",value);
X}
X
Xvoid pr_perms(value)
Xlong value;
X{
X    printf("%lo\n", (value&~S_IFMT) );
X}
X
Xvoid pr_device(value)
Xlong value;
X{
X    printf("%ld,%ld\n", major(value), minor(value));
X}
X
Xvoid pr_user(value)
Xlong value;
X{
X    struct passwd *pw, *getpwuid();
X    if ( (pw=getpwuid((int)value)) == NULL )
X	printf("%ld\n", value);
X    else
X	puts(pw->pw_name);
X}
X
Xvoid pr_group(value)
Xlong value;
X{
X    struct group *gr, *getgrgid();
X    if ( (gr=getgrgid((int)value)) == NULL )
X	printf("%ld\n", value);
X    else
X	puts(gr->gr_name);
X}
X
Xvoid pr_time(value)
Xlong value;
X{
X    char *ctime();
X    fputs(ctime(&value),stdout);
X}
X
Xvoid pr_days(value)
Xlong value;
X{
X    time_t clock, time();
X    (void) time(&clock);
X    printf("%ld\n", ((long)clock-value)/86400L);
X}
X
END_OF_FILE_stat.c
    size="`wc -c < stat.c`"
    if test 7223 -ne "$size" ; then
	echo "stat.c: extraction error - got $size chars"
    fi
fi
if test -f stat.man -a "$1" != "-c" ; then
    echo "stat.man: file exists - will not be overwritten"
else
    echo "x - stat.man (file 3 of 4, 2468 chars)"
    sed -e 's/^X//' << 'END_OF_FILE_stat.man' > stat.man
X.TH STAT 1L
X.SH NAME
Xstat - Displays file status.
X.SH SYNTAX
X.B stat
X-options file ...
X.SH DESCRIPTION
XThe
X.I stat
Xcommand obtains information on a file.  A plethora of options select which
Xitems to display.  Most items have two alternatives:  a lower case letter
Xwhich displays the item in a useful format, and an upper case letter which
Xdisplays the item in a numeric format.  Programmers will recognize these
Xitems as the values returned by the
X.I stat(2)
Xsystem call.  The available options are:
X.sp
X.nf
X.ta \n(.iuC +8 +4n +22n +4n
X.de XX
X	\f2\\$1\fP	\f3\\$2\fP	\\$3	\f3\\$4\fP	\\$5
X..
X.XX dev -d "device major,minor" -D "decimal value"
X.XX inode -i "inode number number" -I "(same information)"
X.XX mode -p "permissions in octal" -P "full mode in octal"
X.XX nlink -n "number of links" -N "(same information)"
X.XX uid -u "name of owning user" -U "number of owning user"
X.XX gid -g "name of owning group" -G "number of owning group"
X.XX rdev -r "device major,minor" -R "decimal value"
X.XX size -s "size in bytes" -S "(same information)"
X.XX atime -a "last access time" -A "seconds since epoch"
X.XX mtime -m "last modify time" -M "seconds since epoch"
X.XX ctime -c "last change time" -C "seconds since epoch"
X.XX "" -x "all above items" -X "all above items"
X.XX mtime -f "days ago" "" ""
X.fi
X.P
XPlease see the
X.I stat(2)
Xmanual page for the details on the items as listed in column one above.
X.P
XIf
X.I stat
Xis invoked for a single piece of information on one file, just that
Xitem is printed.  If more than one piece of information is requested,
Xit will be labelled, for example:
X.RS
X.sp
XUID=root
X.sp
X.RE
XThe labels are derived from the column 1 names, with the "st" prefix
Xdropped and the result converted to upper case.  If information on
Xmore than one file is requested, the filename will precede each line of output.
X.P
XNote there are three special options:
X.B "-x"
Xand
X.B "-X"
Xwhich request a number of options to be used, and the
X.B "-f"
Xoption which requests the modify time in a special format.  The difference
Xbetween the lower-case time options and upper-case time options is that
Xthe former print the date as text, and the latter print the date in number
Xof seconds since the ``epoch''.  The lower-case device options print the
Xdevice major and minor numbers seperated by commas, the upper-case
Xdevice options show a single decimal value.
X
X
X.SH SEE ALSO
Xchgrp(1), chmod(1), chown(1), ls(1), stat(2)
X.SH AUTHOR
XChip Rosenthal
X.br
X<chip@chinacat.unicom.com>
END_OF_FILE_stat.man
    size="`wc -c < stat.man`"
    if test 2468 -ne "$size" ; then
	echo "stat.man: extraction error - got $size chars"
    fi
fi
if test -f Makefile -a "$1" != "-c" ; then
    echo "Makefile: file exists - will not be overwritten"
else
    echo "x - Makefile (file 4 of 4, 1260 chars)"
    sed -e 's/^X//' << 'END_OF_FILE_Makefile' > Makefile
X
X# %Z% %M% %I% %E% %U%
X# Makefile for "stat" (generated by makemake version 1.00.09)
X# Created by bin@chinacat on Sun Jul 29 20:15:19 CDT 1990
X
XSHELL = /bin/sh
XCC = cc
XDEFS = 
XCOPTS = -O
XLOPTS = 
XLIBS = 
XDEBUG = -g -DDEBUG
XLINTFLAGS = -DLINT
X
XTARG = stat
XOTHERS = 
X
XSRCS = stat.c
X
XOBJS = stat.o
X
X# Any edits below this line will be lost if "makemake" is rerun!
X# Commands may be inserted after the '#%custom' line at the end of this file.
X
XCFLAGS = $(COPTS) $(DEFS) # $(DEBUG)
XLFLAGS = $(LOPTS) # $(DEBUG)
X
Xall:		$(TARG) $(OTHERS)
Xinstall:	all		; inst Install
Xclean:				; rm -f $(TARG) $(OBJS) a.out core $(TARG).lint
Xclobber:	clean		; inst -u Install
Xlint:		$(TARG).lint
X
X$(TARG):		$(OBJS)
X		$(CC) $(LFLAGS) -o $@ $(OBJS) $(LIBS)
X
X$(TARG).lint:	$(TARG)
X		lint $(LINTFLAGS) $(DEFS) $(SRCS) $(LIBS) > $@
X
Xstat.o: stat.c
X
Xmake:		;
X		makemake -i -v1.00.09 -aMakefile \
X		    -DSHELL='$(SHELL)' -DCC='$(CC)' -DDEFS='$(DEFS)' \
X		    -DCOPTS='$(COPTS)' -DLOPTS='$(LOPTS)' -DLIBS='$(LIBS)' \
X		    -DDEBUG='$(DEBUG)' -DLINTFLAGS='$(LINTFLAGS)' \
X		    -DOTHERS='$(OTHERS)' $(TARG) $(SRCS)
X
X#%custom - commands below this line will be maintained if 'makemake' is rerun
X
Xshar: stat.shar
X
XLIST = README stat.c stat.man Makefile
X
Xstat.shar:	$(LIST)
X		shar $(LIST) > $@
X
END_OF_FILE_Makefile
    size="`wc -c < Makefile`"
    if test 1260 -ne "$size" ; then
	echo "Makefile: extraction error - got $size chars"
    fi
fi
echo "done - 4 files extracted"
exit 0
-- 
Chip Rosenthal                            |  You aren't some icon carved out
chip@chinacat.Unicom.COM                  |  of soap, sent down here to clean
Unicom Systems Development, 512-482-8260  |  up my reputation.  -John Hiatt

fritz@mercury.caltech.edu (Fritz Nordby) (08/03/90)

Am I missing something here, or will the
following provide the desired function:

#!/bin/sh
case $# in 2);;*)echo "usage: ${0:-newer} file1 file2" >&2;exit 2;;esac
[ ! -f "$2" -o -f "$1" -a "X`/bin/ls -t \"\$1\" \"\$2\"`" = "X$1
$2"] >/dev/null 2>&1


I suppose this will fail if, for example, it is invoked as
$ newer 'foo
foo' 'foo'
but people who put newlines in their filenames get what they deserve!