removed tac, added tail from p9 instead (tac == tail -r) - 9base - revived mini… | |
git clone git://git.suckless.org/9base | |
Log | |
Files | |
Refs | |
README | |
LICENSE | |
--- | |
commit 45ac8c8bd851ba5081939a63dd8a57421abec09a | |
parent 942791ab23de64d2580e3143ba3866ad85fa8ab3 | |
Author: Anselm R Garbe <[email protected]> | |
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]] … | |
+ | |
+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"); | |
+} |