commit 45ac8c8bd851ba5081939a63dd8a57421abec09a
parent 942791ab23de64d2580e3143ba3866ad85fa8ab3
Author: Anselm R Garbe <anselm@garbe.us>
Date: Sun, 11 Apr 2010 19:13:01 +0100
removed tac, added tail from p9 instead (tac == tail -r)
Diffstat:
M | Makefile | | | 2 | +- |
D | tac/Makefile | | | 10 | ---------- |
D | tac/tac.1 | | | 28 | ---------------------------- |
D | tac/tac.c | | | 60 | ------------------------------------------------------------ |
A | tail/Makefile | | | 11 | +++++++++++ |
A | tail/tail.1 | | | 87 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | tail/tail.c | | | 363 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
7 files changed, 462 insertions(+), 99 deletions(-)
diff --git a/Makefile b/Makefile
@@ -4,7 +4,7 @@ include config.mk
SUBDIRS = lib9 yacc awk basename bc cal cat cleanname date dc du echo \
fortune freq getflags grep hoc ls mk mkdir mtime rc read \
- sed seq sleep sort tac tee test touch tr troff uniq
+ sed seq sleep sort tail tee test touch tr troff uniq
# factor primes
diff --git a/tac/Makefile b/tac/Makefile
@@ -1,10 +0,0 @@
-# tac - reverse line order cat
-# Depends on ../lib9
-
-TARG = tac
-
-include ../std.mk
-
-pre-uninstall:
-
-post-install:
diff --git a/tac/tac.1 b/tac/tac.1
@@ -1,28 +0,0 @@
-.TH TAC 1
-.SH NAME
-tac \- reverse concatenate files
-.SH SYNOPSIS
-.B tac
-[
-.I file ...
-]
-.SH DESCRIPTION
-.I Tac
-reads each
-.I file
-in sequence and writes it on the standard output in reverse line order.
-.IP
-.L
-tac file
-.LP
-prints a file in reverse line order
-.IP
-.L
-tac file1 file2 >file3
-.LP
-Concatenate reversed file1 and file2 into file3
-.LP
-.SH SEE ALSO
-.IR cat (1)
-.SH BUGS
-Same as in cat
diff --git a/tac/tac.c b/tac/tac.c
@@ -1,60 +0,0 @@
-/* author: pancake<nopcode.org> */
-#include <u.h>
-#include <libc.h>
-
-static vlong bsize = 0;
-static char *buf;
-#define LINES 4096
-
-void
-tac()
-{
- int i, j;
- char *ptr, **nls;
- nls = malloc(LINES*sizeof(nls));
- for(i=1, ptr=buf; ptr;) {
- assert(nls != NULL);
- for(j=0; j<LINES && (ptr=strchr(ptr+1, '\n')); j++)
- nls[i++] = ptr+1;
- nls = realloc(nls, (i+LINES)*sizeof(nls));
- }
- *nls = buf;
- while(i--)
- write(1, nls[i], nls[i+1]-nls[i]);
- free(nls);
-}
-
-void
-load(int f)
-{
- vlong nsize, size = seek(f, 0, 2);
- if (size>0) {
- nsize = bsize + size;
- buf = realloc(buf, nsize);
- seek(f, 0, 0);
- read(f, buf+bsize, size);
- bsize = nsize;
- } else
- while ((size = read(f, buf+bsize, LINES))>0)
- bsize+=size;
-}
-
-void
-main(int argc, char *argv[])
-{
- int i, f;
- buf = malloc(1);
- assert(buf != NULL);
- if (argc == 1)
- load(0);
- else for(i=1; i<argc; i++){
- f = open(argv[i], OREAD);
- if(f >= 0){
- load(f);
- close(f);
- }else sysfatal("can't open %s: %r", argv[i]);
- }
- tac();
- free(buf);
- exits(0);
-}
diff --git a/tail/Makefile b/tail/Makefile
@@ -0,0 +1,11 @@
+# tail - tail unix port from plan9
+#
+# Depends on ../lib9
+
+TARG = tail
+
+include ../std.mk
+
+pre-uninstall:
+
+post-install:
diff --git a/tail/tail.1 b/tail/tail.1
@@ -0,0 +1,87 @@
+.TH TAIL 1
+.SH NAME
+tail \- deliver the last part of a file
+.SH SYNOPSIS
+.B tail
+[
+.BR +- \fInumber\fP[ lbc ][ rf ]
+]
+[
+.I file
+]
+.PP
+.B tail
+[
+.B -fr
+]
+[
+.B -n
+.I nlines
+]
+[
+.B -c
+.I nbytes
+]
+[
+.I file
+]
+.SH DESCRIPTION
+.I Tail
+copies the named file to the standard output beginning
+at a designated place.
+If no file is named, the standard input is copied.
+.PP
+Copying begins at position
+.BI + number
+measured from the beginning, or
+.BI - number
+from the end of the input.
+.I Number
+is counted in lines, 1K blocks or bytes,
+according to the appended flag
+.LR l ,
+.LR b ,
+or
+.LR c .
+Default is
+.B -10l
+(ten ell).
+.PP
+The further flag
+.L r
+causes tail to print lines from the end of the file in reverse order;
+.L f
+(follow) causes
+.IR tail ,
+after printing to the end, to keep watch and
+print further data as it appears.
+.PP
+The second syntax is that promulgated by POSIX, where
+the
+.I numbers
+rather than the options are signed.
+.SH EXAMPLES
+.TP
+.B tail file
+Print the last 10 lines of a file.
+.TP
+.B tail +0f file
+Print a file, and continue to watch
+data accumulate as it grows.
+.TP
+.B sed 10q file
+Print the first 10 lines of a file.
+.SH SOURCE
+.B \*9/src/cmd/tail.c
+.SH BUGS
+Tails relative to the end of the file
+are treasured up in a buffer, and thus
+are limited in length.
+.PP
+According to custom, option
+.BI + number
+counts lines from 1, and counts
+blocks and bytes from 0.
+.PP
+.I Tail
+is ignorant of UTF.
diff --git a/tail/tail.c b/tail/tail.c
@@ -0,0 +1,363 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include <bio.h>
+
+/*
+ * tail command, posix plus v10 option -r.
+ * the simple command tail -c, legal in v10, is illegal
+ */
+
+vlong count;
+int anycount;
+int follow;
+int file = 0;
+char* umsg = "usage: tail [-n N] [-c N] [-f] [-r] [+-N[bc][fr]] [file]";
+
+Biobuf bout;
+enum
+{
+ BEG,
+ END
+} origin = END;
+enum
+{
+ CHARS,
+ LINES
+} units = LINES;
+enum
+{
+ FWD,
+ REV
+} dir = FWD;
+
+extern void copy(void);
+extern void fatal(char*);
+extern int getnumber(char*);
+extern void keep(void);
+extern void reverse(void);
+extern void skip(void);
+extern void suffix(char*);
+extern long tread(char*, long);
+#define trunc tailtrunc
+extern void trunc(Dir*, Dir**);
+extern vlong tseek(vlong, int);
+extern void twrite(char*, long);
+extern void usage(void);
+
+#define JUMP(o,p) tseek(o,p), copy()
+
+void
+main(int argc, char **argv)
+{
+ int seekable, c;
+
+ Binit(&bout, 1, OWRITE);
+ for(; argc > 1 && ((c=*argv[1])=='-'||c=='+'); argc--,argv++ ) {
+ if(getnumber(argv[1])) {
+ suffix(argv[1]);
+ continue;
+ } else
+ if(c == '-')
+ switch(argv[1][1]) {
+ case 'c':
+ units = CHARS;
+ case 'n':
+ if(getnumber(argv[1]+2))
+ continue;
+ else
+ if(argc > 2 && getnumber(argv[2])) {
+ argc--, argv++;
+ continue;
+ } else
+ usage();
+ case 'r':
+ dir = REV;
+ continue;
+ case 'f':
+ follow++;
+ continue;
+ case '-':
+ argc--, argv++;
+ }
+ break;
+ }
+ if(dir==REV && (units==CHARS || follow || origin==BEG))
+ fatal("incompatible options");
+ if(!anycount)
+ count = dir==REV? ~0ULL>>1: 10;
+ if(origin==BEG && units==LINES && count>0)
+ count--;
+ if(argc > 2)
+ usage();
+ if(argc > 1 && (file=open(argv[1],0)) < 0)
+ fatal(argv[1]);
+ seekable = seek(file,0L,0) == 0;
+
+ if(!seekable && origin==END)
+ keep();
+ else
+ if(!seekable && origin==BEG)
+ skip();
+ else
+ if(units==CHARS && origin==END)
+ JUMP(-count, 2);
+ else
+ if(units==CHARS && origin==BEG)
+ JUMP(count, 0);
+ else
+ if(units==LINES && origin==END)
+ reverse();
+ else
+ if(units==LINES && origin==BEG)
+ skip();
+ if(follow && seekable)
+ for(;;) {
+ static Dir *sb0, *sb1;
+ trunc(sb1, &sb0);
+ copy();
+ trunc(sb0, &sb1);
+ sleep(5000);
+ }
+ exits(0);
+}
+
+void
+trunc(Dir *old, Dir **new)
+{
+ Dir *d;
+ vlong olength;
+
+ d = dirfstat(file);
+ if(d == nil)
+ return;
+ olength = 0;
+ if(old)
+ olength = old->length;
+ if(d->length < olength)
+ d->length = tseek(0L, 0);
+ free(*new);
+ *new = d;
+}
+
+void
+suffix(char *s)
+{
+ while(*s && strchr("0123456789+-", *s))
+ s++;
+ switch(*s) {
+ case 'b':
+ if((count *= 1024) < 0)
+ fatal("too big");
+ case 'c':
+ units = CHARS;
+ case 'l':
+ s++;
+ }
+ switch(*s) {
+ case 'r':
+ dir = REV;
+ return;
+ case 'f':
+ follow++;
+ return;
+ case 0:
+ return;
+ }
+ usage();
+}
+
+/*
+ * read past head of the file to find tail
+ */
+void
+skip(void)
+{
+ int i;
+ long n;
+ char buf[Bsize];
+ if(units == CHARS) {
+ for( ; count>0; count -=n) {
+ n = count<Bsize? count: Bsize;
+ if(!(n = tread(buf, n)))
+ return;
+ }
+ } else /*units == LINES*/ {
+ n = i = 0;
+ while(count > 0) {
+ if(!(n = tread(buf, Bsize)))
+ return;
+ for(i=0; i<n && count>0; i++)
+ if(buf[i]=='\n')
+ count--;
+ }
+ twrite(buf+i, n-i);
+ }
+ copy();
+}
+
+void
+copy(void)
+{
+ long n;
+ char buf[Bsize];
+ while((n=tread(buf, Bsize)) > 0) {
+ twrite(buf, n);
+ Bflush(&bout); /* for FWD on pipe; else harmless */
+ }
+}
+
+/*
+ * read whole file, keeping the tail
+ * complexity is length(file)*length(tail).
+ * could be linear.
+ */
+void
+keep(void)
+{
+ int len = 0;
+ long bufsiz = 0;
+ char *buf = 0;
+ int j, k, n;
+
+ for(n=1; n;) {
+ if(len+Bsize > bufsiz) {
+ bufsiz += 2*Bsize;
+ if(!(buf = realloc(buf, bufsiz+1)))
+ fatal("out of space");
+ }
+ for(; n && len<bufsiz; len+=n)
+ n = tread(buf+len, bufsiz-len);
+ if(count >= len)
+ continue;
+ if(units == CHARS)
+ j = len - count;
+ else {
+ /* units == LINES */
+ j = buf[len-1]=='\n'? len-1: len;
+ for(k=0; j>0; j--)
+ if(buf[j-1] == '\n')
+ if(++k >= count)
+ break;
+ }
+ memmove(buf, buf+j, len-=j);
+ }
+ if(dir == REV) {
+ if(len>0 && buf[len-1]!='\n')
+ buf[len++] = '\n';
+ for(j=len-1 ; j>0; j--)
+ if(buf[j-1] == '\n') {
+ twrite(buf+j, len-j);
+ if(--count <= 0)
+ return;
+ len = j;
+ }
+ }
+ if(count > 0)
+ twrite(buf, len);
+}
+
+/*
+ * count backward and print tail of file
+ */
+void
+reverse(void)
+{
+ int first;
+ long len = 0;
+ long n = 0;
+ long bufsiz = 0;
+ char *buf = 0;
+ vlong pos = tseek(0L, 2);
+
+ for(first=1; pos>0 && count>0; first=0) {
+ n = pos>Bsize? Bsize: (int)pos;
+ pos -= n;
+ if(len+n > bufsiz) {
+ bufsiz += 2*Bsize;
+ if(!(buf = realloc(buf, bufsiz+1)))
+ fatal("out of space");
+ }
+ memmove(buf+n, buf, len);
+ len += n;
+ tseek(pos, 0);
+ if(tread(buf, n) != n)
+ fatal("length error");
+ if(first && buf[len-1]!='\n')
+ buf[len++] = '\n';
+ for(n=len-1 ; n>0 && count>0; n--)
+ if(buf[n-1] == '\n') {
+ count--;
+ if(dir == REV)
+ twrite(buf+n, len-n);
+ len = n;
+ }
+ }
+ if(dir == FWD) {
+ tseek(n==0? 0 : pos+n+1, 0);
+ copy();
+ } else
+ if(count > 0)
+ twrite(buf, len);
+}
+
+vlong
+tseek(vlong o, int p)
+{
+ o = seek(file, o, p);
+ if(o == -1)
+ fatal("");
+ return o;
+}
+
+long
+tread(char *buf, long n)
+{
+ int r = read(file, buf, n);
+ if(r == -1)
+ fatal("");
+ return r;
+}
+
+void
+twrite(char *s, long n)
+{
+ if(Bwrite(&bout, s, n) != n)
+ fatal("");
+}
+
+int
+getnumber(char *s)
+{
+ if(*s=='-' || *s=='+')
+ s++;
+ if(!isdigit((uchar)*s))
+ return 0;
+ if(s[-1] == '+')
+ origin = BEG;
+ if(anycount++)
+ fatal("excess option");
+ count = atol(s);
+
+ /* check range of count */
+ if(count < 0 || (int)count != count)
+ fatal("too big");
+ return 1;
+}
+
+void
+fatal(char *s)
+{
+ char buf[ERRMAX];
+
+ errstr(buf, sizeof buf);
+ fprint(2, "tail: %s: %s\n", s, buf);
+ exits(s);
+}
+
+void
+usage(void)
+{
+ fprint(2, "%s\n", umsg);
+ exits("usage");
+}