LCOV - code coverage report
Current view: top level - src - shuf.c (source / functions) Hit Total Coverage
Test: coreutils.info Lines: 172 182 94.5 %
Date: 2018-01-30 Functions: 7 7 100.0 %

          Line data    Source code
       1             : /* Shuffle lines of text.
       2             : 
       3             :    Copyright (C) 2006, 2007 Free Software Foundation, Inc.
       4             : 
       5             :    This program is free software: you can redistribute it and/or modify
       6             :    it under the terms of the GNU General Public License as published by
       7             :    the Free Software Foundation, either version 3 of the License, or
       8             :    (at your option) any later version.
       9             : 
      10             :    This program is distributed in the hope that it will be useful,
      11             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
      12             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13             :    GNU General Public License for more details.
      14             : 
      15             :    You should have received a copy of the GNU General Public License
      16             :    along with this program.  If not, see <http://www.gnu.org/licenses/>.
      17             : 
      18             :    Written by Paul Eggert.  */
      19             : 
      20             : #include <config.h>
      21             : 
      22             : #include <stdio.h>
      23             : #include <sys/types.h>
      24             : #include "system.h"
      25             : 
      26             : #include "error.h"
      27             : #include "getopt.h"
      28             : #include "quote.h"
      29             : #include "quotearg.h"
      30             : #include "randint.h"
      31             : #include "randperm.h"
      32             : #include "xstrtol.h"
      33             : 
      34             : /* The official name of this program (e.g., no `g' prefix).  */
      35             : #define PROGRAM_NAME "shuf"
      36             : 
      37             : #define AUTHORS "Paul Eggert"
      38             : 
      39             : /* The name this program was run with. */
      40             : char *program_name;
      41             : 
      42             : void
      43          28 : usage (int status)
      44             : {
      45          28 :   if (status != EXIT_SUCCESS)
      46          27 :     fprintf (stderr, _("Try `%s --help' for more information.\n"),
      47             :              program_name);
      48             :   else
      49             :     {
      50           1 :       printf (_("\
      51             : Usage: %s [OPTION]... [FILE]\n\
      52             :   or:  %s -e [OPTION]... [ARG]...\n\
      53             :   or:  %s -i LO-HI [OPTION]...\n\
      54             : "),
      55             :               program_name, program_name, program_name);
      56           1 :       fputs (_("\
      57             : Write a random permutation of the input lines to standard output.\n\
      58             : \n\
      59             : "), stdout);
      60           1 :       fputs (_("\
      61             : Mandatory arguments to long options are mandatory for short options too.\n\
      62             : "), stdout);
      63           1 :       fputs (_("\
      64             :   -e, --echo                treat each ARG as an input line\n\
      65             :   -i, --input-range=LO-HI   treat each number LO through HI as an input line\n\
      66             :   -n, --head-lines=LINES    output at most LINES lines\n\
      67             :   -o, --output=FILE         write result to FILE instead of standard output\n\
      68             :       --random-source=FILE  get random bytes from FILE (default /dev/urandom)\n\
      69             :   -z, --zero-terminated     end lines with 0 byte, not newline\n\
      70             : "), stdout);
      71           1 :       fputs (HELP_OPTION_DESCRIPTION, stdout);
      72           1 :       fputs (VERSION_OPTION_DESCRIPTION, stdout);
      73           1 :       fputs (_("\
      74             : \n\
      75             : With no FILE, or when FILE is -, read standard input.\n\
      76             : "), stdout);
      77           1 :       emit_bug_reporting_address ();
      78             :     }
      79             : 
      80          28 :   exit (status);
      81             : }
      82             : 
      83             : /* For long options that have no equivalent short option, use a
      84             :    non-character as a pseudo short option, starting with CHAR_MAX + 1.  */
      85             : enum
      86             : {
      87             :   RANDOM_SOURCE_OPTION = CHAR_MAX + 1
      88             : };
      89             : 
      90             : static struct option const long_opts[] =
      91             : {
      92             :   {"echo", no_argument, NULL, 'e'},
      93             :   {"input-range", required_argument, NULL, 'i'},
      94             :   {"head-count", required_argument, NULL, 'n'},
      95             :   {"output", required_argument, NULL, 'o'},
      96             :   {"random-source", required_argument, NULL, RANDOM_SOURCE_OPTION},
      97             :   {"zero-terminated", no_argument, NULL, 'z'},
      98             :   {GETOPT_HELP_OPTION_DECL},
      99             :   {GETOPT_VERSION_OPTION_DECL},
     100             :   {0, 0, 0, 0},
     101             : };
     102             : 
     103             : static bool
     104          85 : input_numbers_option_used (size_t lo_input, size_t hi_input)
     105             : {
     106          85 :   return ! (lo_input == SIZE_MAX && hi_input == 0);
     107             : }
     108             : 
     109             : static void
     110           8 : input_from_argv (char **operand, int n_operands, char eolbyte)
     111             : {
     112             :   char *p;
     113           8 :   size_t size = n_operands;
     114             :   int i;
     115             : 
     116          15 :   for (i = 0; i < n_operands; i++)
     117           7 :     size += strlen (operand[i]);
     118           8 :   p = xmalloc (size);
     119             : 
     120          15 :   for (i = 0; i < n_operands; i++)
     121             :     {
     122           7 :       char *p1 = stpcpy (p, operand[i]);
     123           7 :       operand[i] = p;
     124           7 :       p = p1;
     125           7 :       *p++ = eolbyte;
     126             :     }
     127             : 
     128           8 :   operand[n_operands] = p;
     129           8 : }
     130             : 
     131             : /* Return the start of the next line after LINE.  The current line
     132             :    ends in EOLBYTE, and is guaranteed to end before LINE + N.  */
     133             : 
     134             : static char *
     135         232 : next_line (char *line, char eolbyte, size_t n)
     136             : {
     137         232 :   char *p = memchr (line, eolbyte, n);
     138         232 :   return p + 1;
     139             : }
     140             : 
     141             : /* Read data from file IN.  Input lines are delimited by EOLBYTE;
     142             :    silently append a trailing EOLBYTE if the file ends in some other
     143             :    byte.  Store a pointer to the resulting array of lines into *PLINE.
     144             :    Return the number of lines read.  Report an error and exit on
     145             :    failure.  */
     146             : 
     147             : static size_t
     148          17 : read_input (FILE *in, char eolbyte, char ***pline)
     149             : {
     150             :   char *p;
     151          17 :   char *buf = NULL;
     152             :   char *lim;
     153          17 :   size_t alloc = 0;
     154          17 :   size_t used = 0;
     155          17 :   size_t next_alloc = (1 << 13) + 1;
     156             :   size_t bytes_to_read;
     157             :   size_t nread;
     158             :   char **line;
     159             :   size_t i;
     160             :   size_t n_lines;
     161             :   int fread_errno;
     162             :   struct stat instat;
     163             : 
     164          17 :   if (fstat (fileno (in), &instat) == 0 && S_ISREG (instat.st_mode))
     165             :     {
     166          12 :       off_t file_size = instat.st_size;
     167          12 :       off_t current_offset = ftello (in);
     168          12 :       if (0 <= current_offset)
     169             :         {
     170          12 :           off_t remaining_size =
     171          12 :             (current_offset < file_size ? file_size - current_offset : 0);
     172          12 :           if (SIZE_MAX - 2 < remaining_size)
     173           0 :             xalloc_die ();
     174          12 :           next_alloc = remaining_size + 2;
     175             :         }
     176             :     }
     177             : 
     178             :   do
     179             :     {
     180          17 :       if (alloc <= used + 1)
     181             :         {
     182          17 :           if (alloc == SIZE_MAX)
     183           0 :             xalloc_die ();
     184          17 :           alloc = next_alloc;
     185          17 :           next_alloc = alloc * 2;
     186          17 :           if (next_alloc < alloc)
     187           0 :             next_alloc = SIZE_MAX;
     188          17 :           buf = xrealloc (buf, alloc);
     189             :         }
     190             : 
     191          17 :       bytes_to_read = alloc - used - 1;
     192          17 :       nread = fread (buf + used, sizeof (char), bytes_to_read, in);
     193          17 :       used += nread;
     194             :     }
     195          17 :   while (nread == bytes_to_read);
     196             : 
     197          17 :   fread_errno = errno;
     198             : 
     199          17 :   if (used && buf[used - 1] != eolbyte)
     200           3 :     buf[used++] = eolbyte;
     201             : 
     202          17 :   lim = buf + used;
     203             : 
     204          17 :   n_lines = 0;
     205         133 :   for (p = buf; p < lim; p = next_line (p, eolbyte, lim - p))
     206         116 :     n_lines++;
     207             : 
     208          17 :   *pline = line = xnmalloc (n_lines + 1, sizeof *line);
     209             : 
     210          17 :   line[0] = p = buf;
     211         133 :   for (i = 1; i <= n_lines; i++)
     212         116 :     line[i] = p = next_line (p, eolbyte, lim - p);
     213             : 
     214          17 :   errno = fread_errno;
     215          17 :   return n_lines;
     216             : }
     217             : 
     218             : static int
     219          23 : write_permuted_output (size_t n_lines, char * const *line, size_t lo_input,
     220             :                        size_t const *permutation)
     221             : {
     222             :   size_t i;
     223             : 
     224          23 :   if (line)
     225         111 :     for (i = 0; i < n_lines; i++)
     226             :       {
     227          91 :         char * const *p = line + permutation[i];
     228          91 :         size_t len = p[1] - p[0];
     229          91 :         if (fwrite (p[0], sizeof *p[0], len, stdout) != len)
     230           0 :           return -1;
     231             :       }
     232             :   else
     233           6 :     for (i = 0; i < n_lines; i++)
     234             :       {
     235           3 :         unsigned long int n = lo_input + permutation[i];
     236           3 :         if (printf ("%lu\n", n) < 0)
     237           0 :           return -1;
     238             :       }
     239             : 
     240          23 :   return 0;
     241             : }
     242             : 
     243             : int
     244          72 : main (int argc, char **argv)
     245             : {
     246          72 :   bool echo = false;
     247          72 :   size_t lo_input = SIZE_MAX;
     248          72 :   size_t hi_input = 0;
     249          72 :   size_t head_lines = SIZE_MAX;
     250          72 :   char const *outfile = NULL;
     251          72 :   char *random_source = NULL;
     252          72 :   char eolbyte = '\n';
     253          72 :   char **input_lines = NULL;
     254             : 
     255             :   int optc;
     256             :   int n_operands;
     257             :   char **operand;
     258             :   size_t n_lines;
     259             :   char **line;
     260             :   struct randint_source *randint_source;
     261             :   size_t *permutation;
     262             : 
     263             :   initialize_main (&argc, &argv);
     264          72 :   program_name = argv[0];
     265          72 :   setlocale (LC_ALL, "");
     266             :   bindtextdomain (PACKAGE, LOCALEDIR);
     267             :   textdomain (PACKAGE);
     268             : 
     269          72 :   atexit (close_stdout);
     270             : 
     271         172 :   while ((optc = getopt_long (argc, argv, "ei:n:o:z", long_opts, NULL)) != -1)
     272          50 :     switch (optc)
     273             :       {
     274          15 :       case 'e':
     275          15 :         echo = true;
     276          15 :         break;
     277             : 
     278          16 :       case 'i':
     279             :         {
     280          16 :           unsigned long int argval = 0;
     281          16 :           char *p = strchr (optarg, '-');
     282          16 :           char const *hi_optarg = optarg;
     283          16 :           bool invalid = !p;
     284             : 
     285          16 :           if (input_numbers_option_used (lo_input, hi_input))
     286           1 :             error (EXIT_FAILURE, 0, _("multiple -i options specified"));
     287             : 
     288          15 :           if (p)
     289             :             {
     290          13 :               *p = '\0';
     291          26 :               invalid = ((xstrtoul (optarg, NULL, 10, &argval, NULL)
     292             :                           != LONGINT_OK)
     293          13 :                          || SIZE_MAX < argval);
     294          13 :               *p = '-';
     295          13 :               lo_input = argval;
     296          13 :               hi_optarg = p + 1;
     297             :             }
     298             : 
     299          30 :           invalid |= ((xstrtoul (hi_optarg, NULL, 10, &argval, NULL)
     300             :                        != LONGINT_OK)
     301          15 :                       || SIZE_MAX < argval);
     302          15 :           hi_input = argval;
     303          15 :           n_lines = hi_input - lo_input + 1;
     304          15 :           invalid |= ((lo_input <= hi_input) == (n_lines == 0));
     305          15 :           if (invalid)
     306          10 :             error (EXIT_FAILURE, 0, _("invalid input range %s"),
     307             :                    quote (optarg));
     308             :         }
     309           5 :         break;
     310             : 
     311           2 :       case 'n':
     312             :         {
     313             :           unsigned long int argval;
     314           2 :           strtol_error e = xstrtoul (optarg, NULL, 10, &argval, NULL);
     315             : 
     316           2 :           if (e == LONGINT_OK)
     317           1 :             head_lines = MIN (head_lines, argval);
     318           1 :           else if (e != LONGINT_OVERFLOW)
     319           1 :             error (EXIT_FAILURE, 0, _("invalid line count %s"),
     320             :                    quote (optarg));
     321             :         }
     322           1 :         break;
     323             : 
     324           4 :       case 'o':
     325           4 :         if (outfile && !STREQ (outfile, optarg))
     326           0 :           error (EXIT_FAILURE, 0, _("multiple output files specified"));
     327           4 :         outfile = optarg;
     328           4 :         break;
     329             : 
     330           2 :       case RANDOM_SOURCE_OPTION:
     331           2 :         if (random_source && !STREQ (random_source, optarg))
     332           0 :           error (EXIT_FAILURE, 0, _("multiple random sources specified"));
     333           2 :         random_source = optarg;
     334           2 :         break;
     335             : 
     336           1 :       case 'z':
     337           1 :         eolbyte = '\0';
     338           1 :         break;
     339             : 
     340           1 :       case_GETOPT_HELP_CHAR;
     341           1 :       case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
     342           8 :       default:
     343           8 :         usage (EXIT_FAILURE);
     344             :       }
     345             : 
     346          50 :   n_operands = argc - optind;
     347          50 :   operand = argv + optind;
     348             : 
     349          50 :   if (echo)
     350             :     {
     351           8 :       if (input_numbers_option_used (lo_input, hi_input))
     352           0 :         error (EXIT_FAILURE, 0, _("cannot combine -e and -i options"));
     353           8 :       input_from_argv (operand, n_operands, eolbyte);
     354           8 :       n_lines = n_operands;
     355           8 :       line = operand;
     356             :     }
     357          42 :   else if (input_numbers_option_used (lo_input, hi_input))
     358             :     {
     359           4 :       if (n_operands)
     360             :         {
     361           1 :           error (0, 0, _("extra operand %s\n"), quote (operand[0]));
     362           1 :           usage (EXIT_FAILURE);
     363             :         }
     364           3 :       n_lines = hi_input - lo_input + 1;
     365           3 :       line = NULL;
     366             :     }
     367             :   else
     368             :     {
     369          38 :       switch (n_operands)
     370             :         {
     371           9 :         case 0:
     372           9 :           break;
     373             : 
     374          11 :         case 1:
     375          11 :           if (! (STREQ (operand[0], "-") || freopen (operand[0], "r", stdin)))
     376           3 :             error (EXIT_FAILURE, errno, "%s", operand[0]);
     377           8 :           break;
     378             : 
     379          18 :         default:
     380          18 :           error (0, 0, _("extra operand %s"), quote (operand[1]));
     381          18 :           usage (EXIT_FAILURE);
     382             :         }
     383             : 
     384          17 :       n_lines = read_input (stdin, eolbyte, &input_lines);
     385          17 :       line = input_lines;
     386             :     }
     387             : 
     388          28 :   head_lines = MIN (head_lines, n_lines);
     389             : 
     390          28 :   randint_source = randint_all_new (random_source,
     391             :                                     randperm_bound (head_lines, n_lines));
     392          28 :   if (! randint_source)
     393           1 :     error (EXIT_FAILURE, errno, "%s", quotearg_colon (random_source));
     394             : 
     395             :   /* Close stdin now, rather than earlier, so that randint_all_new
     396             :      doesn't have to worry about opening something other than
     397             :      stdin.  */
     398          27 :   if (! (echo || input_numbers_option_used (lo_input, hi_input))
     399          16 :       && (ferror (stdin) || fclose (stdin) != 0))
     400           3 :     error (EXIT_FAILURE, errno, _("read error"));
     401             : 
     402          24 :   permutation = randperm_new (randint_source, head_lines, n_lines);
     403             : 
     404          23 :   if (outfile && ! freopen (outfile, "w", stdout))
     405           0 :     error (EXIT_FAILURE, errno, "%s", quotearg_colon (outfile));
     406          23 :   if (write_permuted_output (head_lines, line, lo_input, permutation) != 0)
     407           0 :     error (EXIT_FAILURE, errno, _("write error"));
     408             : 
     409             : #ifdef lint
     410             :   free (permutation);
     411             :   randint_all_free (randint_source);
     412             :   if (input_lines)
     413             :     {
     414             :       free (input_lines[0]);
     415             :       free (input_lines);
     416             :     }
     417             : #endif
     418             : 
     419          23 :   return EXIT_SUCCESS;
     420             : }

Generated by: LCOV version 1.10