9base

revived minimalist port of Plan 9 userland to Unix
git clone git://git.suckless.org/9base
Log | Files | Refs | README | LICENSE

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:
MMakefile | 2+-
Dtac/Makefile | 10----------
Dtac/tac.1 | 28----------------------------
Dtac/tac.c | 60------------------------------------------------------------
Atail/Makefile | 11+++++++++++
Atail/tail.1 | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atail/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"); +}