v13i033: Print BSD accounting file

Rich Salz rsalz at bbn.com
Fri Feb 12 00:42:05 AEST 1988


Submitted-by: Roy Smith <phri!roy>
Posting-number: Volume 13, Issue 33
Archive-name: printacct

[  This package displays the contents of the /usr/adm/acct files in
   human-readable format, so you can use things like awk and perl to
   print "better" accounting records.  It will probably need work to
   run with the SystemV accounting stuff.  --r$ ]

# This is a shell archive.  Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file".
# Contents:  README Makefile pracct.1 pracct.c yesterday.c accounting
 
echo x - README
sed 's/^@//' > "README" <<'@//E*O*F README//'
Mon Jan 25 23:15:18 EST 1988

	This is a little program I whipped up to help me do user cpu usage
accounting.  On the surface, /etc/sa (or /usr/etc/sa if you're on a Sun) does
cpu usage accounting, but it does so much damn processing that I kept finding
myself being frustrated because I couldn't get exactly the statistics I
wanted.  Very un-unix-like, sa is.  Doesn't let the user get at the actual
data to do what he wants with it.

	My answer was to just convert the fields in /usr/adm/acct to ascii and
hack together awk scripts to pull out whatever bits of information I happened
to want that month.  Maybe not very efficient, but easy.  Contained herein is
the program to print the /usr/adm/acct file, a man page, and a sample shell
script that we run out of crontab at 15 minutes past midnight every day.  We
run this on a network of Sun workstations, all of which remote mount a common
/usr/local.  Since root permissions don't apply accross NFS mount points, I
made /usr/local/acct owned by user "acct", and su to that when it comes time
to move the daily summary file there.  You can't just make the whole script
suid acct because it has to remove /usr/adm/acct (aka /private/usr/adm/acct),
which needs root permission.  Isn't NFS fun?

	One last note.  I like to keep each day's stats in a file named for
that day.  Since we run the stats for day X a few minutes into day X+1, I
can't just use date to generate file names, because the names would be off by
one day.  I whipped up yesterday.c to solve this problem.  A nicer solution
(but decidedly feeping creatureism) would be to expand the Sys5 date to accept
a (signed) offset which is added to the current date before going through the
variable output formatting code.

	This was developed on SunOS-3.[02] systems, and hasn't been tested on
any other systems.  I believe the format of /usr/adm/acct is BSD specific, so
it probably won't run on Sys5 machines unmodified.

	This code is public domain.  Do with it what you want, including
selling it for profit if you can find somebody stupid enough to pay money for
it.  All I ask is that you retain this notice with the code, and keep my name
on it.

Roy Smith, System Administrator
Public Health Research Institute
455 First Avenue, New York, NY 10016

UUCP: {allegra,philabs,seismo!cmcl2}!phri!roy
@//E*O*F README//
chmod u=rw,g=r,o=r README
 
echo x - Makefile
sed 's/^@//' > "Makefile" <<'@//E*O*F Makefile//'
#
# Makefile for pracct
#
# $Header: Makefile,v 1.3 86/12/09 17:41:42 roy Exp $
# Copyright 1986 Roy Smith
#
# $Log:	Makefile,v $
# Revision 1.3  86/12/09  17:41:42  roy
# Added rules for yesterday.  Deleted rules for lint.
# 
# Revision 1.2  86/11/29  23:30:18  roy
# Added rule to co source file if needed.
# Added rule for "make lint"
# 
# Revision 1.1  86/11/29  23:12:19  roy
# Initial revision
# 
#

CFLAGS = -O

programs:	pracct yesterday

pracct:		pracct.o
		cc $(CFLAGS) -o pracct pracct.o

pracct.o:	pracct.c
		cc $(CFLAGS) -c pracct.c

pracct.c:	RCS/pracct.c,v
		co pracct.c

yesterday:	yesterday.o
		cc $(CFLAGS) -o yesterday yesterday.o

yesterday.o:	yesterday.c
		cc $(CFLAGS) -c yesterday.c

yesterday.c:	RCS/yesterday.c,v
		co yesterday.c
@//E*O*F Makefile//
chmod u=rw,g=r,o=r Makefile
 
echo x - pracct.1
sed 's/^@//' > "pracct.1" <<'@//E*O*F pracct.1//'
@.TH DATE 1 "25 Jan 1988"
@.SH NAME
pracct \- print accounting information in human readable form
@.SH SYNOPSIS
@.B pracct
[-v] [-a file] [-f [cvVsSxXeEbBuUgGmitTfF]]
@.SH DESCRIPTION
@.I Pracct
prints out the system accounting file in human-readable form with a minimum
of processing.
This is sort of a RISC version of
@.I /etc/sa.
As the man page says,
@.I sa
has a near google of options; unfortunately, it is rare that it has exactly
the right one.
With
@.I pracct,
you can simply get an ascii dump of the fields of interest and
use
@.I awk
(for example) to calculate whatever statistics you desire.
The options are many:
@.TP
@.B -v
Verbose output; each field is tagged with a (hopefully) descriptive label.
@.TP
@.BI -a " file"
Alternate accounting file, used instead of default
@.I
/usr/adm/acct.
@.TP
@.BI -f " flags"
Fields to print.
Fields are printed in the order specified.
Default is
@.B UcXB.
Fields are separated with spaces, and an attempt is made to ensure that no
spaces appear within fields (i.e. -f F will give `F----' instead of
`F\0\0\0\0').
This may be defeated, however, by command, user, group, or tty names
containing spaces within them.
Also,
@.B
-B
gives a date in ctime format, which has embedded spaces.
The fields are:
@.TP
@.B
c
Command name
@.TP
@.B
v
Numeric user CPU time (seconds)
@.TP
@.B
V
Symbolic user time (DD+HH:MM:SS)
@.TP
@.B
s
Numeric system time (seconds)
@.TP
@.B
S
Symbolic system time (DD+HH:MM:SS)
@.TP
@.B
x
User+system time (seconds)
@.TP
@.B
X
Symbolic user+system time (DD+HH:MM:SS)
@.TP
@.B
e
Numeric elapsed time (seconds)
@.TP
@.B
E
Symbolic elapsed time (DD+HH:MM:SS)
@.TP
@.B
b
Numeric beginning time (seconds)
@.TP
@.B
B
Symbolic beginning time (ctime format)
@.TP
@.B
u
Numeric user I.D.
@.TP
@.B
U
Login name
@.TP
@.B
g
Numeric group I.D.
@.TP
@.B
G
Group name
@.TP
@.B
m
Average memory usage
@.TP
@.B
i
Disk I/O blocks
@.TP
@.B
t
Major/minor device for control tty
@.TP
@.B
T
Name of control tty (/dev/tty??); currently unimplemented
@.TP
@.B
f
Accounting flags in octal
@.TP
@.B
F
Flags as FSCDX
@.SH "SEE ALSO"
sa(8), acct(2)
@.SH AUTHOR
Roy Smith <phri!roy>
@//E*O*F pracct.1//
chmod u=rw,g=r,o=r pracct.1
 
echo x - pracct.c
sed 's/^@//' > "pracct.c" <<'@//E*O*F pracct.c//'
/*
 * Pracct.c -- print out the system accounting file with a minimum of
 * processing.  This is sort of a RISC version of /etc/sa.  As the man page
 * says, sa has a near google of options; unfortunately, it is rare that
 * it has exactly the right one.  With pracct, you can simply get an ascii
 * dump of the fields of interest and let awk do the the rest.
 */

# ifndef LINT
static char rcsid[] = "$Header: pracct.c,v 1.7 88/01/25 22:57:16 roy Exp $";
# endif

/*
 * $Log:	pracct.c,v $
 * Revision 1.7  88/01/25  22:57:16  roy
 * Removed copyright notice; code placed in public domain.
 * 
 * Revision 1.6  87/11/17  15:10:55  roy
 * Added uid/gid name caches.
 * 
 * Revision 1.5  86/12/02  17:31:43  roy
 * Fixed but with printing ac_comm -- if command name is full
 * length of ac_comm[], it isn't null terminated; we now are
 * careful not to run past this.  Changed symbolic flag format
 * to print '-' instead of ' ' for missing flags to keep the number
 * of fields on an output line constant.
 * 
 * Revision 1.4  86/12/02  14:34:52  roy
 * Added support for -v (verbose) and -a
 * (alternate accounting file) options.
 * 
 * Revision 1.3  86/12/02  00:33:21  roy
 * Added command-line option processing using getopt.
 * 
 * Revision 1.2  86/11/29  23:29:16  roy
 * Fixed rcsid string (capitalized $ H e a d e r : $).
 * Added type casts to keep lint happy.
 * 
 * Revision 1.1  86/11/29  23:07:16  roy
 * Initial revision
 * 
 */

# include <sys/types.h>
# include <sys/acct.h>
# include <sys/file.h>
# include <sys/time.h>
# include <pwd.h>
# include <grp.h>
# include <stdio.h>

# define ACCT_FILE	"/usr/adm/acct"	/* raw accounting data file */
# define DFLT_FLDS	"UcXB"		/* default fields to print */
# define FIELD_SEP	' '		/* how to delimit fields on output */
# define GETOPT_FMT	"va:f:"		/* permissible command-line flags */
# define USAGE_MSG	"pracct [-v] [-a file] [-f [cvVsSxXeEbBuUgGmitTfF]]"

/*
 * Symbolic names and flags for fields in acct(5) structure
 */
# define COMM		'c'	/* command name */
# define UTIME		'v'	/* numeric user CPU time (seconds) */
# define SYM_UTIME	'V'	/* symbolic user time (DD+HH:MM:SS) */
# define STIME		's'	/* numeric system time (seconds) */
# define SYM_STIME	'S'	/* symbolic system time (DD+HH:MM:SS) */
# define SUMTIME	'x'	/* user+system time (seconds) */
# define SYM_SUMTIME	'X'	/* symbolic user+system time (DD+HH:MM:SS) */
# define ETIME		'e'	/* numeric elapsed time (seconds) */
# define SYM_ETIME	'E'	/* symbolic elapsed time (DD+HH:MM:SS) */
# define BTIME		'b'	/* numeric beginning time (seconds) */
# define SYM_BTIME	'B'	/* symbolic beginning time (ctime format) */
# define UID		'u'	/* numeric user I.D. */
# define SYM_UID	'U'	/* login name */
# define GID		'g'	/* numeric group I.D. */
# define SYM_GID	'G'	/* group name */
# define MEM		'm'	/* average memory usage */
# define IO		'i'	/* disk I/O blocks */
# define TTY		't'	/* major/minor device for control tty */
# define SYM_TTY	'T'	/* name of control tty /dev/tty?? */
# define FLAG		'f'	/* flags in octal */
# define SYM_FLAG	'F'	/* flags as FSCDX */

/*
 * Name cache stuff.
 */
# define NC_NUID	256	/* size of uid cache; power of 2 */
# define NC_UIDMASK	0xff	/* log base 2 (NC_NUID) - 1 */
# define NC_NGID	64	/* size of gid cache;  power of 2 */
# define NC_GIDMASK	0x3f	/* log base 2 (NC_NGID) - 1 */

struct nc
{
	int nc_n;		/* numeric uid or gid */
	char *nc_name;		/* user or group name; NULL if unused */
} unc[NC_NUID], gnc[NC_NGID];

main (argc, argv)
int argc;
char *argv[];
{
	struct acct ac;
	int fd, acsize, first, nbytes, verbose, n, maxcom;
	long utime, stime, etime, sumtime, btime;
	char *fields, *f, *ct, *ctime(), *dhms(), *uid2name(), *gid2name();
	char c, *afile;
	extern int optind;
	extern char *optarg;

	fields = DFLT_FLDS;
	verbose = 0;
	afile = ACCT_FILE;

	/*
	 * figure out the maximum number of characters
	 * stored for each command name.
	 */
	maxcom = sizeof (ac.ac_comm) / sizeof (*ac.ac_comm);

	while ((c = getopt (argc, argv, GETOPT_FMT)) != EOF)
	{
		switch (c)
		{
		case 'f':
			fields = optarg;
			break;

		case 'v':
			verbose = 1;
			break;

		case 'a':
			afile = optarg;
			break;

		case '?':
			fprintf (stderr, "pracct: illegal option (-%c)\n", c);
			fprintf (stderr, "pracct: usage: %s\n", USAGE_MSG);
			exit (1);

		default:
			fprintf (stderr, "bad return from getopt (%o)!\n", c);
			abort ();
		}
	}

	/*
	 * Open accounting file for reading.
	 */
	if ((fd = open (afile, O_RDONLY, 0)) < 0)
	{
		perror ("pracct: can't open accounting file");
		exit (1);
	}

	/*
	 * Flush name cache.
	 */
	for (n = 0; n < NC_NUID; n++)
		unc[n].nc_name = NULL;

	for (n = 0; n < NC_NGID; n++)
		gnc[n].nc_name = NULL;

	/*
	 * Read the accounting file, one accounting-structure-worth
	 * at a time, until EOF or error.
	 */
	acsize = sizeof (ac) / sizeof (char);
	while ((nbytes = read (fd, (char *) &ac, acsize)) == acsize)
	{
		/*
		 * Convert from snazzy compressed floating point format to
		 * plain old integers.  It's easier to just do all the
		 * conversions than to check to see which ones we actually
		 * need.  We often want sumtime, so we rarely lose big.
		 * This may get changed later if it proves to be important.
		 */
		utime = expand (ac.ac_utime);
		stime = expand (ac.ac_stime);
		sumtime = utime + stime;
		etime = expand (ac.ac_etime);

		/*
		 * Step though the desired fields in the order specified.
		 * Insert a field delimiter in front of every field except
		 * for the first one.  Warning: fields may have internal
		 * blanks, i.e. those in ctime format.
		 */
		first = 1;
		for (f = fields; *f != NULL; f++)
		{
			if (first)
				first = 0;
			else
				putchar (FIELD_SEP);

			switch (*f)
			{
			case COMM:
				/*
				 * We have to be careful not to print past
				 * the end of the string because the command
				 * name is only null terminated if it is
				 * less than maxcom characters long.
				 */
				printf ("%-*.*s", maxcom, maxcom, ac.ac_comm);
				continue;

			case UTIME:
				printf ("%8ld", utime);
				if (verbose)
					printf ("user");
				continue;

			case SYM_UTIME:
				printf ("%11s", dhms (utime));
				if (verbose)
					printf ("user");
				continue;

			case STIME:
				printf ("%8ld", stime);
				if (verbose)
					printf ("sys");
				continue;

			case SYM_STIME:
				printf ("%11s", dhms (stime));
				if (verbose)
					printf ("sys");
				continue;

			case SUMTIME:
				printf ("%8ld", sumtime);
				if (verbose)
					printf ("u+s");
				continue;

			case SYM_SUMTIME:
				printf ("%11s", dhms (sumtime));
				if (verbose)
					printf ("u+s");
				continue;

			case ETIME:
				printf ("%8ld", etime);
				if (verbose)
					printf ("real");
				continue;

			case SYM_ETIME:
				printf ("%11s", dhms (etime));
				if (verbose)
					printf ("real");
				continue;

			case BTIME:
				printf ("%10ld", (long) ac.ac_btime);
				if (verbose)
					printf ("begin");
				continue;

			case SYM_BTIME:
				/*
				 * Beginning time in ctime format.  Ctime
				 * does us a favor and sticks a newline at
				 * the end of the string, which we then have
				 * to be careful not to print.
				 */
				btime = ac.ac_btime;
				ct = ctime (&btime);
				while (*ct != '\n'  && *ct != NULL)
					putchar (*(ct++));
				if (verbose)
					printf (" begin");
				continue;

			case UID:
				printf ("%5d", ac.ac_uid);
				if (verbose)
					printf ("uid");
				continue;

			case SYM_UID:
				printf ("%-8s", uid2name (ac.ac_uid));
				continue;

			case GID:
				printf ("%5d", ac.ac_gid);
				if (verbose)
					printf ("gid");
				continue;

			case SYM_GID:
				printf ("%-8s", gid2name (ac.ac_gid));
				continue;

			case MEM:
				printf ("%5d", ac.ac_mem);
				if (verbose)
					printf ("mem");
				continue;

			case IO:
				printf ("%10ld", (long) expand (ac.ac_io));
				if (verbose)
					printf ("io");
				continue;

			case TTY:
				if (verbose)
				{
					printf ("%3d", major (ac.ac_tty));
					printf ("/%3d", minor (ac.ac_tty));
				}
				else
				{
					printf ("%3d", major (ac.ac_tty));
					printf (" %3d", minor (ac.ac_tty));
				}
				continue;

			case SYM_TTY:
				printf ("no SYM_TTY yet");
				continue;

			case FLAG:
				printf ("%03o", ac.ac_flag);
				continue;

			case SYM_FLAG:
				putchar (ac.ac_flag & AFORK   ? 'F' : '-');
				putchar (ac.ac_flag & ASU     ? 'S' : '-');
				putchar (ac.ac_flag & ACOMPAT ? 'C' : '-');
				putchar (ac.ac_flag & ACORE   ? 'D' : '-');
				putchar (ac.ac_flag & AXSIG   ? 'X' : '-');
				continue;
			}
		}
		/*
		 * After all the required fields, end the line.
		 */
		putchar ('\n');
	}

	if (nbytes > 0)
	{
		fprintf (stderr, "pracct: short read on accounting file\n");
		exit (1);
	}
	if (nbytes < 0)
	{
		perror ("pracct: read error on accounting file");
		exit (1);
	}
	exit (0);
}

/*
 * Dhms -- convert time in seconds to DD+HH:MM:SS format.  If
 * time is negative or greater than 99+23:59:59, return all ?'s.
 * Result is returned as a fixed-width, null-terminated, static
 * string which is overwritten on each call.
 */
char *dhms (time)
register long time;	/* , no see :-) */
{
	int sec, min, hour, day;
	static char buf[(sizeof ("DD+HH:MM:SS") / sizeof (char)) + 1];

	sec = time % 60;
	time = time / 60;
	min = time % 60;
	time = time / 60;
	hour = time % 24;
	time = time / 24;
	day = time;

	if (time < 0 || day > 99)
		sprintf (buf, "??+??:??:??");
	else
		sprintf (buf, "%02d+%02d:%02d:%02d", day, hour, min, sec);

	return (buf);
}

/*
 * Expand -- convert time from compressed floating point format to normal
 * integer.  This routine is based on one in the 4.2BSD /etc/sa.c source
 * file.  Warning: this depends on the format of t being exactly as in
 * 4.2BSD -- 3 bit power-of-8 exponent in high-order bits and 13 bit
 * mantissa in low-order bits.
 */
time_t expand (t)
comp_t t;
{
	register time_t xtime;
	register exp;

	/*
	 * Get mantissa and exponent.
	 */
	xtime = t & 017777;
	exp = (t >> 13) & 07;

	/*
	 * Compute time = mantissa * 8^exp.
	 */
	while (exp != 0)
	{
		exp--;
		xtime <<= 3;
	}

	return(xtime);
}

/*
 * Uid2name -- convert a numeric user i.d. to a login name by looking it
 * up in /etc/passwd.  The login name is returned as a null-terminated,
 * static string which is overwritten on each call.  A name cache speeds
 * this up many fold (without it, over two-thirds of the user cpu time can
 * be spent here).
 */
char *uid2name (uid)
register int uid;
{
	struct passwd *getpwuid(), *pw;
	register struct nc *entry;
	char *malloc();
	register char *name, *temp;
	register int l;

	entry = &unc[uid & NC_UIDMASK];
	if (entry->nc_name == NULL || entry->nc_n != uid)
	{
		entry->nc_n = uid;
		if (entry->nc_name != NULL)
			free (entry->nc_name);

		if ((pw = getpwuid (uid)) == NULL)
			name = "???";
		else
			name = pw->pw_name;

		l = 0;
		for (temp = name; *temp != NULL; temp++)
			l++;
		if ((entry->nc_name = malloc (l+1)) == NULL)
		{
			fprintf (stderr, "out of memory in uid2name\n");
			exit (1);
		}
		temp = entry->nc_name;
		while ((*(temp++) = *(name++)) != NULL)
			;
	}
	return (entry->nc_name);
}

/*
 * Gid2name -- convert a numeric group i.d. to a group name by
 * looking it up in /etc/group.  Similar to uid2name, q.v.
 */
char *gid2name (gid)
register int gid;
{
	struct group *getgrgid(), *gr;
	register struct nc *entry;
	char *malloc();
	register char *name, *temp;
	register int l;

	entry = &gnc[gid & NC_GIDMASK];
	if (entry->nc_name == NULL || entry->nc_n != gid)
	{
		entry->nc_n = gid;
		if (entry->nc_name != NULL)
			free (entry->nc_name);

		if ((gr = getgrgid (gid)) == NULL)
			name = "???";
		else
			name = gr->gr_name;

		l = 0;
		for (temp = name; *temp != NULL; temp++)
			l++;
		if ((entry->nc_name = malloc (l+1)) == NULL)
		{
			fprintf (stderr, "out of memory in gid2name\n");
			exit (1);
		}
		temp = entry->nc_name;
		while ((*(temp++) = *(name++)) != NULL)
			;
	}
	return (entry->nc_name);
}
@//E*O*F pracct.c//
chmod u=r,g=r,o=r pracct.c
 
echo x - yesterday.c
sed 's/^@//' > "yesterday.c" <<'@//E*O*F yesterday.c//'
/*
 * Yesterday -- print out yesterday's date.
 */

# ifndef LINT
static char rcsid[] = "$Header: yesterday.c,v 1.3 88/01/25 22:57:34 roy Exp $";
# endif

/*
 * $Log:	yesterday.c,v $
 * Revision 1.3  88/01/25  22:57:34  roy
 * Removed copyright notice; code placed in public domain.
 * 
 * Revision 1.2  86/12/09  17:40:44  roy
 * Fixed typo (prerror->perror) and added missing arguments to printf.
 * 
 */

# include <sys/time.h>
# define SEC_PER_DAY	(60*60*24)	/* Let's see them change this one! */

char *months[] =
{
	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};

char *days[] =
{
	"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};

main ()
{
	register struct tm *timebuf;
	struct timeval t;
	struct timezone tz;
	long yesterday;
	int status;

	status = 0;
	if ((status = gettimeofday (&t, &tz)) < 0)
	{
		perror ("yesterday: gettimeofday failed");
		yesterday = 0;
	}
	else
	{
		yesterday = t.tv_sec - SEC_PER_DAY;
	}

	timebuf = localtime (&yesterday);

	printf ("%s %s %2d 19%02d\n",	days[timebuf->tm_wday],
					months[timebuf->tm_mon],
					timebuf->tm_mday,
					timebuf->tm_year);
	exit (status);
}
@//E*O*F yesterday.c//
chmod u=r,g=r,o=r yesterday.c
 
echo x - accounting
sed 's/^@//' > "accounting" <<'@//E*O*F accounting//'
#!/bin/sh

#
# Generate per-user cpu time stats.  Stats are kept in $acctdir/hostname/date.
# Note that since we do this a few minutes after midnight, date is yesterday,
# not today.  If $acctdir/hostname doesn't exist, we create it.
#

#
# Change these to put things in different places
#
pracct=/usr/local/etc/pracct
yesterday=/usr/local/etc/yesterday
acctdir=/usr/local/acct

#
# Make a copy of the accounting file, then do the normal daily summary
# and truncate the accounting file.  Accounting is temporarily turned
# off while we snarf a copy of the file and do the statistics to keep
# things in sync.
#
/usr/etc/accton
cp /usr/adm/acct /tmp/acct1.$$
/usr/etc/sa -s > /dev/null
rm /usr/adm/acct
touch /usr/adm/acct
/usr/etc/accton /usr/adm/acct

#
# Generate the per-user stats.
#
$pracct -a /tmp/acct1.$$ -f Ux | awk '
		{time[$1] = time[$1] + $2}
		END {for (name in time) print name, time[name]}
	' > /tmp/acct2.$$

rm -f /tmp/acct1.$$

#
# Make sure a directory exists for this machine.  If not, try to create it.
# Move daily summary temp file to accounting directory.  This must be done
# as user "acct" so we can write on the common accounting directory.
#
su acct << EOF_SU_ACCT
	if test ! -d $acctdir/`hostname`
	then
		mkdir $acctdir/`hostname`
	fi
	cp /tmp/acct2.$$ $acctdir/`hostname`/`$yesterday | tr ' ' '-'`
EOF_SU_ACCT

rm -f /tmp/acct2.$$
@//E*O*F accounting//
chmod u=rwx,g=rx,o=rx accounting
 
echo Inspecting for damage in transit...
temp=/tmp/shar$$; dtemp=/tmp/.shar$$
trap "rm -f $temp $dtemp; exit" 0 1 2 3 15
cat > $temp <<\!!!
      43     405    2316 README
      40     114     755 Makefile
     135     366    2061 pracct.1
     499    1907   11883 pracct.c
      59     170    1153 yesterday.c
      52     215    1364 accounting
     828    3177   19532 total
!!!
wc  README Makefile pracct.1 pracct.c yesterday.c accounting | sed 's=[^ ]*/==' | diff -b $temp - >$dtemp
if [ -s $dtemp ]
then echo "Ouch [diff of wc output]:" ; cat $dtemp
else echo "No problems found."
fi
exit 0
-- 
Roy Smith, {allegra,cmcl2,philabs}!phri!roy
System Administrator, Public Health Research Institute
455 First Avenue, New York, NY 10016

-- 
For comp.sources.unix stuff, mail to sources at uunet.uu.net.



More information about the Comp.sources.unix mailing list