Signed-off-by: Mattias Andrée <[email protected]>
---
 Makefile |   1 +
 README   |   1 +
 shuf.1   |  26 +++++++++++
 shuf.c   | 149 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 177 insertions(+)
 create mode 100644 shuf.1
 create mode 100644 shuf.c

diff --git a/Makefile b/Makefile
index 6b2bfdf..e407663 100644
--- a/Makefile
+++ b/Makefile
@@ -149,6 +149,7 @@ BIN =\
        sha512sum\
        sha512-224sum\
        sha512-256sum\
+       shuf\
        sleep\
        sort\
        split\
diff --git a/README b/README
index d60d8fc..e752a09 100644
--- a/README
+++ b/README
@@ -78,6 +78,7 @@ The following tools are implemented:
 0=*|x sha512sum       .
 0=* x sha512-224sum   .
 0=* x sha512-256sum   .
+0=* x shuf            (-e, -i, -n, -o, -z)
 0=*|o sleep           .
 0#*|o sort            .
 0=*|o split           .
diff --git a/shuf.1 b/shuf.1
new file mode 100644
index 0000000..ce0dcd0
--- /dev/null
+++ b/shuf.1
@@ -0,0 +1,26 @@
+.Dd 2016-03-26
+.Dt SHUF 1
+.Os sbase
+.Sh NAME
+.Nm shuf
+.Nd shuffle the lines in a file
+.Sh SYNOPSIS
+.Nm
+.Op Fl r
+.Op Fl s Ar source
+.Op Ar file
+.Sh DESCRIPTION
+.Nm
+reads each line from
+.Ar file
+and prints its line in random order.
+.Nm
+reads from stdin.
+.Sh OPTIONS
+.Bl -tag -width Ds
+.It Fl r
+Output random lines ad infinitum.
+.It Fl s Ar source
+Get random bytes from
+.Ar source .
+.El
diff --git a/shuf.c b/shuf.c
new file mode 100644
index 0000000..a325bb2
--- /dev/null
+++ b/shuf.c
@@ -0,0 +1,149 @@
+/* See LICENSE file for copyright and license details. */
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "text.h"
+#include "util.h"
+
+static int source = -1;
+static char *sflag = 0;
+static int (*random_byte)(void);
+
+static void
+usage(void)
+{
+       eprintf("usage: %s [-r] [-s source] [file]\n", argv0);
+}
+
+static int
+random_byte_file(void)
+{
+       unsigned char r;
+       ssize_t n = read(source, &r, 1);
+       if (n < 0)
+               eprintf("read %s:", sflag);
+       if (!n)
+               eprintf("read %s: end of file reached\n");
+       return r;
+}
+
+static int
+random_byte_libc(void)
+{
+       double r;
+       r = rand();
+       r /= (double)RAND_MAX + 1;
+       r *= 256;
+       return (int)r;
+}
+
+static size_t
+random_int(size_t max)
+{
+       size_t n = max;
+       size_t r = 0;
+       size_t mask = max;
+       int s = 1;
+
+       while (((mask + 1) & ~mask) != (mask + 1))
+               mask |= mask >> s++;
+
+       do {
+               while (n) {
+                       n >>= 8;
+                       r <<= 8;
+                       r |= random_byte();
+               }
+               r &= mask;
+       } while (r > max);
+
+       return r;
+}
+
+static void
+shuf(FILE *fp, int repeat)
+{
+       struct linebuf lines = EMPTY_LINEBUF;
+       struct line line;
+       size_t i, j;
+
+       getlines(fp, &lines);
+
+       if (source < 0) {
+               srand((intptr_t)(lines.lines) | time(NULL));
+               random_byte = random_byte_libc;
+       } else {
+               random_byte = random_byte_file;
+       }
+
+       if (repeat) {
+               if (!lines.nlines)
+                       eprintf("no lines to repeat\n");
+               for (;;) {
+                       i = random_int(lines.nlines - 1);
+                       line = lines.lines[i];
+                       fwrite(line.data, 1, line.len, stdout);
+               }
+       }
+
+       for (i = lines.nlines; i--;) {
+               j = random_int(i);
+               line = lines.lines[i];
+               lines.lines[i] = lines.lines[j];
+               lines.lines[j] = line;
+       }
+       for (i = lines.nlines; i--;) {
+               line = lines.lines[i];
+               fwrite(line.data, 1, line.len, stdout);
+       }
+}
+
+int
+main(int argc, char *argv[])
+{
+       FILE *fp;
+       int rflag = 0;
+       int ret = 0;
+
+       ARGBEGIN {
+       case 'r':
+               rflag = 1;
+               break;
+       case 's':
+               if (source >= 0)
+                       close(source);
+               source = open(sflag = EARGF(usage()), O_RDONLY);
+               if (source < 0)
+                       eprintf("open %s:", sflag);
+               break;
+       default:
+               usage();
+       } ARGEND
+
+       if (argc > 1)
+               usage();
+
+       if (!argc) {
+               shuf(stdin, rflag);
+       } else {
+               if (!strcmp(*argv, "-")) {
+                       *argv = "<stdin>";
+                       fp = stdin;
+               } else if (!(fp = fopen(*argv, "r"))) {
+                       eprintf("fopen %s:", *argv);
+               }
+               shuf(fp, rflag);
+               if (fp != stdin && fshut(fp, *argv))
+                       ret = 1;
+       }
+
+       ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>");
+       if (source >= 0)
+               close(source);
+
+       return ret;
+}
-- 
2.7.3


Reply via email to