[net.sources] "head", a complement to tail

ok@edai.UUCP (Richard O'Keefe) (03/06/84)

----
head is a command for getting prefixes of files.
cat >head.1 <<'EOF'
.TH HEAD 1 Edinburgh
.SH NAME
head \- deliver the first part of a file
.SH SYNOPSIS
.B head
[-\fBh\fR] [-number[\fBbclp\fR]] [file]...
.SH DESCRIPTION
.I Head
copies the first part of each of the named files to the standard output.
If no file is named, the standard input is used.
.PP
Copying continues for
.I number
units from the beginning, where a unit is
.B (b)
a block of 512 characters,
.B (c)
a character,
.B (l)
a line (terminated by and including a LineFeed), or
.B (p)
a page (terminated by and including a FormFeed).
.PP
If no unit is given, the default is Lines.
If no number is given, the default is 10.
If neither a number nor a unit is given, the hyphen should be omitted
as well, as a single hyphen is accepted as a file name signifying the
standard input.
.PP
You may have more than one -number-unit specification.  Each applies
to all the files to its right until the next one.
The default for an omitted number or unit is "same as last time".
.PP
If the \fBh\fR flag is given, each file's prefix will be preceded
by a newline, the file's name, a colon, and a newline.  A common
use for -h is to skim several files, and it helps to see which is which.
This flag may only given when units=line or pages.
.SH EXAMPLES
Copy the first 10 lines of the standard input:
.PP
	head
.PP
Copy the first page of each C file in this directory:
.PP
	head -1p *.c
.PP
Copy the first 2 lines of a, the first 5 lines of b and c, all
of d, and the first 4 lines of e:
.PP
	head -2 a -5 b c -1000b d -4l e
.PP
Check the first 10 lines of each Pascal file, but as there are
lots of them, ask to be reminded which is which:
.PP
	head -h *.p
.SH "SEE ALSO"
tail(1)
.SH DIAGNOSTICS
.I head
will complain if it can't open a file, if you try to read the standard
input twice, or if you give an invalid unit specification.  Only in the
last case will it stop at once.  The return code is 0 if all went well,
1 if there was an error.
'EOF'
cat >head.c <<'EOF'
/*% cc -n -s -O % -o head

	head [-number[blpc]] file... [-number[blpc]] file... ...

    Copies an initial segment of each named file in turn to the standard
    output.  If no file is specified, the standard input is used.  The
    standard input may also be specified by the "file" `-'.

    The size of the initial segment is the given number (default 10) of
	Characters (c)
	Blocks	   (b)		which are taken to be 512 characters long
	Lines	   (l)		which terminate at and including LineFeeds
	Pages	   (p)		which terminate at and including FormFeeds
    If no unit is specified, the first length specifier will default to
    lines and remaining specifiers to the same unit as the previous one.
    For example, head -2b foo -3 baz
    will copy three blocks from baz, not three lines.
*/

#define	Chars	1		/* characters per unit */
#define	Blocks	512		/* characters per unit */
#define	Lines	10		/* unit's terminating character (LF) */
#define	Pages	12		/* unit's terminating character (FF) */

#define	None	0		/* no file processed yet */
#define	StdIn	2		/* stdin has been read */
#define	Other	1		/* some other file has been read */


void do_file(fd, number, units)
    int fd;			/* UNIX file descriptor */
    long number;		/* how many units to copy */
    int units;			/* what sort of units to copy */
    {
	char buffer[4096];	/* input buffer */
	long nread;

	if (units == Lines || units == Pages) {
	    while ((nread = read(fd, buffer, sizeof buffer)) > 0) {
		register char *p = buffer;
		register int n = nread;
		while (--n >= 0)
		    if (*p++ == units && --number == 0) {
			write(1, buffer, nread - n);
			return;
		    }
		write(1, buffer, nread);
	    }
	} else {
	    /* units == Chars || units == Blocks */
	    number *= units;
	    while ((nread = read(fd, buffer, sizeof buffer)) > 0) {
		if (nread <= number) {
		    write(1, buffer, nread);
		    number -= nread;
		} else {
		    write(1, buffer, number);
		    return;
		}
	    }
	}
    }


void main(argc, argv)
    int argc;
    char **argv;
    {
	long number = 10;	/* number of units to copy */
	int  units  = Lines;	/* what sort of units to copy */
	int  readin = None;	/* what files have we read */
	int  haderr = 0;	/* did we have to miss a file? */
	int  header = 0;	/* was the -h flag given? */

	while (--argc > 0) {
	    if (**++argv != '-') {
		int fd = open(*argv, 0);

		if (fd < 0) {
		    write(2, "head: ", 6);
		    perror(*argv);
		    haderr = 1;
		} else {
		    if (header) printf("%s:\n", *argv);
		    do_file(fd, number, units);
		    close(fd);
		}
		readin |= Other;
	    } else
	    if (argv[0][1] == '\0') {
		if (readin & StdIn) {
		    static char gripe[] = "head: can't read stdin twice\n";
		    write(2, gripe, sizeof gripe);
		    haderr = 1;
		} else {
		    if (header) write(1, "-:\n", 4);
		    do_file(0, number, units);
		    readin |= StdIn;
		}
	    } else {
		register char *s = *argv + 1;
		register int n = 0;

		while (*s >= '0' && *s <= '9') n *= 10, n += *s++ - '0';
		if (n != 0) number = n;
		switch (*s|32) {
		    case 'h': header = 1;	break;
		    case 'n': header = 0;	break;
		    case 'b': units = Blocks;	break;
		    case 'c': units = Chars;	break;
		    case 'l': units = Lines;	break;
		    case 'p': units = Pages;	break;
		    case ' '  : /* no change */	break;
		    default : {
			static char gripe[] = "head: units are C,B,L,or P\n";
			write(2, gripe, sizeof gripe);
			exit(1);
		    }
		}
		if (header && units != Lines && units != Pages) {
		    static char gripe[] = "head: -h requires -l or -p\n";
		    write(2, gripe, sizeof gripe);
		    header = 0, haderr = 1;
		}
	    }
	}
	if (readin == None) do_file(0, number, units);
	exit(haderr);
    }

'EOF'

I hope you find this as useful as I do.