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);
 }

Reply via email to