Hello all. A few days ago I needed to reverse a few files line-by-line (i.e., make first line become last and vice versa). There was nothing to do that in base, and rev(1) utility only may reverse characters in lines. So I decided to improve rev(1)... here it is.
While digging there I realized that rev(1) didn't respect the case when input is not newline-terminated, and always append '\n'. I preserved this behaviour for now, but maybe this should be fixed? Thanks for your time! -- WBR, Vadim Zhukov Index: rev.1 =================================================================== RCS file: /cvs/src/usr.bin/rev/rev.1,v retrieving revision 1.7 diff -u -p -r1.7 rev.1 --- rev.1 16 Aug 2009 09:41:08 -0000 1.7 +++ rev.1 29 Dec 2010 00:35:57 -0000 @@ -38,12 +38,42 @@ .Nd reverse lines of a file .Sh SYNOPSIS .Nm rev +.Op Fl cms .Op Ar .Sh DESCRIPTION The .Nm rev utility copies the specified files to the standard output, reversing the -order of characters in every line. +order of characters in every line or the order of lines in the all input files. If no files are specified, the standard input is read. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl c +Reverse order of characters in every line. +This is default. +.It Fl m +Reverse order of lines. +Lines are kept in memory. +This method is fast but is obviously limited by system and user memory limits. +.It Fl s +Reverse order of lines. +Lines are kept in temporary file. +This method is slower than +.Fl m +but is suitable for large input files (if you have enough space on your drive, +of course). +.El +.Pp +Only last option specified is used. +.Sh ENVIRONMENT +.Bl -tag -width Fl +.It Ev TMPDIR +Path in which to store temporary file +.Fl ( s +mode only). +.El +.Sh EXIT STATUS +.Ex -std rev .Sh SEE ALSO .Xr cat 1 Index: rev.c =================================================================== RCS file: /cvs/src/usr.bin/rev/rev.c,v retrieving revision 1.10 diff -u -p -r1.10 rev.c --- rev.c 27 Oct 2009 23:59:42 -0000 1.10 +++ rev.c 29 Dec 2010 00:35:57 -0000 @@ -2,6 +2,7 @@ /* $NetBSD: rev.c,v 1.5 1995/09/28 08:49:40 tls Exp $ */ /*- + * Copyright (c) 2010 Vadim Zhukov <persg...@gmail.com> * Copyright (c) 1987, 1992, 1993 * The Regents of the University of California. All rights reserved. * @@ -31,27 +32,66 @@ */ #include <sys/types.h> +#include <sys/queue.h> #include <err.h> #include <errno.h> +#include <stddef.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> -void usage(void); +static void usage(void); +static int rev_line_chars(const char *p, size_t len); +static int mem_file_line(const char *p, size_t len); +static int swap_file_line(const char *p, size_t len); +static void print_mem(void); +static void print_swap(void); + +#define fatal(msg...) err(EXIT_FAILURE, msg) + +size_t max_line_length_seen; + +/* Non-swap containers */ +SLIST_HEAD(, line) lines = SLIST_HEAD_INITIALIZER(lines); +struct line { + SLIST_ENTRY(line) next; + char text[]; +}; + +/* Swap containers */ +FILE *sfp; + int main(int argc, char *argv[]) { - char *filename, *p, *t; + char *filename, *p; FILE *fp; size_t len; - int ch, rval; + int ch, rval, frval, ffailed; + + enum { + RM_CHARS, + RM_LINES_MEM, + RM_LINES_SWAP + } rev_mode = RM_CHARS; - while ((ch = getopt(argc, argv, "")) != -1) + while ((ch = getopt(argc, argv, "cms")) != -1) switch(ch) { - case '?': + case 'c': + rev_mode = RM_CHARS; + break; + + case 'm': + rev_mode = RM_LINES_MEM; + break; + + case 's': + rev_mode = RM_LINES_SWAP; + break; + default: usage(); } @@ -59,32 +99,66 @@ main(int argc, char *argv[]) argc -= optind; argv += optind; + if (rev_mode == RM_LINES_SWAP) + if ((sfp = tmpfile()) == NULL) + fatal("creating swap file"); + fp = stdin; filename = "stdin"; rval = 0; do { if (*argv) { if ((fp = fopen(*argv, "r")) == NULL) { - warn("%s", *argv); + warn("fopen: %s", *argv); rval = 1; ++argv; continue; } filename = *argv++; } + + ffailed = 0; while ((p = fgetln(fp, &len)) != NULL) { if (p[len - 1] == '\n') --len; - for (t = p + len - 1; t >= p; --t) - putchar(*t); - putchar('\n'); - } - if (ferror(fp)) { - warn("%s", filename); - rval = 1; + + switch(rev_mode) { + case RM_CHARS: + frval = rev_line_chars(p, len); + break; + + case RM_LINES_MEM: + frval = mem_file_line(p, len); + break; + + case RM_LINES_SWAP: + frval = swap_file_line(p, len); + break; + } + + if (frval == -1 && !ffailed) { + warn("reading: %s", filename); + rval = EXIT_FAILURE; + ffailed = 1; + } } + (void)fclose(fp); } while(*argv); + + switch (rev_mode) { + case RM_CHARS: /* please compiler */ + break; + + case RM_LINES_MEM: + print_mem(); + break; + + case RM_LINES_SWAP: + print_swap(); + break; + } + exit(rval); } @@ -93,6 +167,106 @@ usage(void) { extern char *__progname; - (void)fprintf(stderr, "usage: %s [file ...]\n", __progname); + (void)fprintf(stderr, "usage: %s [-cms] [file ...]\n", + __progname); exit(1); +} + +int +rev_line_chars(const char *p, size_t len) { + const char *t; + + for (t = p + len - 1; t >= p; --t) + if (putchar(*t) == EOF) + return -1; + return (putchar('\n') == EOF ? -1 : 0); +} + +int +mem_file_line(const char *p, size_t len) { + struct line *l; + + l = calloc(1, sizeof(struct line) + len + 1); + if (l == NULL) + return -1; + strlcpy(l->text, p, len + 1); + SLIST_INSERT_HEAD(&lines, l, next); + return 0; +} + +void +print_mem(void) { + struct line *l; + + while (!SLIST_EMPTY(&lines)) { + l = SLIST_FIRST(&lines); + if (puts(l->text) == -1) + fatal("print_mem: puts"); + SLIST_REMOVE_HEAD(&lines, next); + free(l); + } +} + +int +swap_file_line(const char *p, size_t len) { + if (len > max_line_length_seen) + max_line_length_seen = len; + if (len > 0 && fwrite(p, len, 1, sfp) < 1) + return -1; + if (fwrite(&len, sizeof(size_t), 1, sfp) < 1) + return -1; + return 0; +} + +void +print_swap(void) { + off_t off, pos; + size_t len; + char *buf; + const char *errstr = NULL; + + buf = malloc(max_line_length_seen + 1); + if (buf == NULL) + fatal("print_swap: malloc"); + + len = 0; + while ((pos = ftello(sfp)) > (off_t)len) { + /* Read length of saved line */ + off = -(off_t)(sizeof(size_t) + len); + if (fseeko(sfp, off, SEEK_CUR) == -1) { + errstr = "fseeko#1"; + goto err; + } + if (fread(&len, sizeof(size_t), 1, sfp) < 1) { + errstr = "fread#1"; + goto err; + } + + if (len > max_line_length_seen) { /* paranoid check */ + errstr = "len > max_line_length_seen"; + goto err; + } + + /* Read line itself */ + off = -(off_t)(sizeof(size_t) + len); + if (fseeko(sfp, off, SEEK_CUR) == -1) { + errstr = "fseeko#2"; + goto err; + } + if (len > 0 && fread(buf, len, 1, sfp) < 1) { + errstr = "fread#2"; + goto err; + } + buf[len] = '\0'; + + if (puts(buf) == EOF) + fatal("print_swap: puts"); + } + free(buf); + fclose(sfp); + if (pos == (off_t)len) + return; + +err: + fatal("print_swap: swap file damaged before %s", errstr); }