* NEWS: Add a line for 6.5-cvs.
[coreutils.git] / src / shuf.c
blob68003c2593657f6d6c13f7ec2bfc1ae5be9284e4
1 /* Shuffle lines of text.
3 Copyright (C) 2006 Free Software Foundation, Inc.
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 2, or (at your option)
8 any later version.
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.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 Written by Paul Eggert. */
21 #include <config.h>
23 #include <stdio.h>
24 #include <sys/types.h>
25 #include "system.h"
27 #include "error.h"
28 #include "getopt.h"
29 #include "quote.h"
30 #include "quotearg.h"
31 #include "randint.h"
32 #include "randperm.h"
33 #include "xstrtol.h"
35 /* The official name of this program (e.g., no `g' prefix). */
36 #define PROGRAM_NAME "shuf"
38 #define AUTHORS "Paul Eggert"
40 /* The name this program was run with. */
41 char *program_name;
43 void
44 usage (int status)
46 if (status != EXIT_SUCCESS)
47 fprintf (stderr, _("Try `%s --help' for more information.\n"),
48 program_name);
49 else
51 printf (_("\
52 Usage: %s [OPTION]... [FILE]\n\
53 or: %s -e [OPTION]... [ARG]...\n\
54 or: %s -i LO-HI [OPTION]...\n\
55 "),
56 program_name, program_name, program_name);
57 fputs (_("\
58 Write a random permutation of the input lines to standard output.\n\
59 \n\
60 "), stdout);
61 fputs (_("\
62 Mandatory arguments to long options are mandatory for short options too.\n\
63 "), stdout);
64 fputs (_("\
65 -e, --echo treat each ARG as an input line\n\
66 -i, --input-range=LO-HI treat each number LO through HI as an input line\n\
67 -n, --head-lines=LINES output at most LINES lines\n\
68 -o, --output=FILE write result to FILE instead of standard output\n\
69 --random-source=FILE get random bytes from FILE (default /dev/urandom)\n\
70 -z, --zero-terminated end lines with 0 byte, not newline\n\
71 "), stdout);
72 fputs (HELP_OPTION_DESCRIPTION, stdout);
73 fputs (VERSION_OPTION_DESCRIPTION, stdout);
74 fputs (_("\
75 \n\
76 With no FILE, or when FILE is -, read standard input.\n\
77 "), stdout);
78 printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
81 exit (status);
84 /* For long options that have no equivalent short option, use a
85 non-character as a pseudo short option, starting with CHAR_MAX + 1. */
86 enum
88 RANDOM_SOURCE_OPTION = CHAR_MAX + 1
91 static struct option const long_opts[] =
93 {"echo", no_argument, NULL, 'e'},
94 {"input-range", required_argument, NULL, 'i'},
95 {"head-count", required_argument, NULL, 'n'},
96 {"output", required_argument, NULL, 'o'},
97 {"random-source", required_argument, NULL, RANDOM_SOURCE_OPTION},
98 {"zero-terminated", no_argument, NULL, 'z'},
99 {GETOPT_HELP_OPTION_DECL},
100 {GETOPT_VERSION_OPTION_DECL},
101 {0, 0, 0, 0},
104 static bool
105 input_numbers_option_used (size_t lo_input, size_t hi_input)
107 return ! (lo_input == SIZE_MAX && hi_input == 0);
110 static void
111 input_from_argv (char **operand, int n_operands, char eolbyte)
113 char *p;
114 size_t size = n_operands;
115 int i;
117 for (i = 0; i < n_operands; i++)
118 size += strlen (operand[i]);
119 p = xmalloc (size);
121 for (i = 0; i < n_operands; i++)
123 char *p1 = stpcpy (p, operand[i]);
124 operand[i] = p;
125 p = p1;
126 *p++ = eolbyte;
129 operand[n_operands] = p;
132 /* Return the start of the next line after LINE. The current line
133 ends in EOLBYTE, and is guaranteed to end before LINE + N. */
135 static char *
136 next_line (char *line, char eolbyte, size_t n)
138 char *p = memchr (line, eolbyte, n);
139 return p + 1;
142 /* Read data from file IN. Input lines are delimited by EOLBYTE;
143 silently append a trailing EOLBYTE if the file ends in some other
144 byte. Store a pointer to the resulting array of lines into *PLINE.
145 Return the number of lines read. Report an error and exit on
146 failure. */
148 static size_t
149 read_input (FILE *in, char eolbyte, char ***pline)
151 char *p;
152 char *buf = NULL;
153 char *lim;
154 size_t alloc = 0;
155 size_t used = 0;
156 size_t next_alloc = (1 << 13) + 1;
157 size_t bytes_to_read;
158 size_t nread;
159 char **line;
160 size_t i;
161 size_t n_lines;
162 int fread_errno;
163 struct stat instat;
165 if (fstat (fileno (in), &instat) == 0 && S_ISREG (instat.st_mode))
167 off_t file_size = instat.st_size;
168 off_t current_offset = ftello (in);
169 if (0 <= current_offset)
171 off_t remaining_size =
172 (current_offset < file_size ? file_size - current_offset : 0);
173 if (SIZE_MAX - 2 < remaining_size)
174 xalloc_die ();
175 next_alloc = remaining_size + 2;
181 if (alloc <= used + 1)
183 if (alloc == SIZE_MAX)
184 xalloc_die ();
185 alloc = next_alloc;
186 next_alloc = alloc * 2;
187 if (next_alloc < alloc)
188 next_alloc = SIZE_MAX;
189 buf = xrealloc (buf, alloc);
192 bytes_to_read = alloc - used - 1;
193 nread = fread (buf + used, sizeof (char), bytes_to_read, in);
194 used += nread;
196 while (nread == bytes_to_read);
198 fread_errno = errno;
200 if (used && buf[used - 1] != eolbyte)
201 buf[used++] = eolbyte;
203 lim = buf + used;
205 n_lines = 0;
206 for (p = buf; p < lim; p = next_line (p, eolbyte, lim - p))
207 n_lines++;
209 *pline = line = xnmalloc (n_lines + 1, sizeof *line);
211 line[0] = p = buf;
212 for (i = 1; i <= n_lines; i++)
213 line[i] = p = next_line (p, eolbyte, lim - p);
215 errno = fread_errno;
216 return n_lines;
219 static int
220 write_permuted_output (size_t n_lines, char * const *line, size_t lo_input,
221 size_t const *permutation)
223 size_t i;
225 if (line)
226 for (i = 0; i < n_lines; i++)
228 char * const *p = line + permutation[i];
229 size_t len = p[1] - p[0];
230 if (fwrite (p[0], sizeof *p[0], len, stdout) != len)
231 return -1;
233 else
234 for (i = 0; i < n_lines; i++)
236 unsigned long int n = lo_input + permutation[i];
237 if (printf ("%lu\n", n) < 0)
238 return -1;
241 return 0;
245 main (int argc, char **argv)
247 bool echo = false;
248 size_t lo_input = SIZE_MAX;
249 size_t hi_input = 0;
250 size_t head_lines = SIZE_MAX;
251 char const *outfile = NULL;
252 char *random_source = NULL;
253 char eolbyte = '\n';
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 const *permutation;
263 initialize_main (&argc, &argv);
264 program_name = argv[0];
265 setlocale (LC_ALL, "");
266 bindtextdomain (PACKAGE, LOCALEDIR);
267 textdomain (PACKAGE);
269 atexit (close_stdout);
271 while ((optc = getopt_long (argc, argv, "ei:n:o:z", long_opts, NULL)) != -1)
272 switch (optc)
274 case 'e':
275 echo = true;
276 break;
278 case 'i':
280 unsigned long int argval = 0;
281 char *p = strchr (optarg, '-');
282 char const *hi_optarg = optarg;
283 bool invalid = !p;
285 if (input_numbers_option_used (lo_input, hi_input))
286 error (EXIT_FAILURE, 0, _("multiple -i options specified"));
288 if (p)
290 *p = '\0';
291 invalid = ((xstrtoul (optarg, NULL, 10, &argval, NULL)
292 != LONGINT_OK)
293 || SIZE_MAX < argval);
294 *p = '-';
295 lo_input = argval;
296 hi_optarg = p + 1;
299 invalid |= ((xstrtoul (hi_optarg, NULL, 10, &argval, NULL)
300 != LONGINT_OK)
301 || SIZE_MAX < argval);
302 hi_input = argval;
303 n_lines = hi_input - lo_input + 1;
304 invalid |= ((lo_input <= hi_input) == (n_lines == 0));
305 if (invalid)
306 error (EXIT_FAILURE, 0, _("invalid input range %s"),
307 quote (optarg));
309 break;
311 case 'n':
313 unsigned long int argval;
314 strtol_error e = xstrtoul (optarg, NULL, 10, &argval, NULL);
316 if (e == LONGINT_OK)
317 head_lines = MIN (head_lines, argval);
318 else if (e != LONGINT_OVERFLOW)
319 error (EXIT_FAILURE, 0, _("invalid line count %s"),
320 quote (optarg));
322 break;
324 case 'o':
325 if (outfile && !STREQ (outfile, optarg))
326 error (EXIT_FAILURE, 0, _("multiple output files specified"));
327 outfile = optarg;
328 break;
330 case RANDOM_SOURCE_OPTION:
331 if (random_source && !STREQ (random_source, optarg))
332 error (EXIT_FAILURE, 0, _("multiple random sources specified"));
333 random_source = optarg;
334 break;
336 case 'z':
337 eolbyte = '\0';
338 break;
340 case_GETOPT_HELP_CHAR;
341 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
342 default:
343 usage (EXIT_FAILURE);
346 n_operands = argc - optind;
347 operand = argv + optind;
349 if (echo)
351 if (input_numbers_option_used (lo_input, hi_input))
352 error (EXIT_FAILURE, 0, _("cannot combine -e and -i options"));
353 input_from_argv (operand, n_operands, eolbyte);
354 n_lines = n_operands;
355 line = operand;
357 else if (input_numbers_option_used (lo_input, hi_input))
359 if (n_operands)
361 error (0, 0, _("extra operand %s\n"), quote (operand[0]));
362 usage (EXIT_FAILURE);
364 n_lines = hi_input - lo_input + 1;
365 line = NULL;
367 else
369 char **input_lines;
371 switch (n_operands)
373 case 0:
374 break;
376 case 1:
377 if (! (STREQ (operand[0], "-") || freopen (operand[0], "r", stdin)))
378 error (EXIT_FAILURE, errno, "%s", operand[0]);
379 break;
381 default:
382 error (0, 0, _("extra operand %s"), quote (operand[1]));
383 usage (EXIT_FAILURE);
386 n_lines = read_input (stdin, eolbyte, &input_lines);
387 line = input_lines;
390 head_lines = MIN (head_lines, n_lines);
392 randint_source = randint_all_new (random_source,
393 randperm_bound (head_lines, n_lines));
394 if (! randint_source)
395 error (EXIT_FAILURE, errno, "%s", quotearg_colon (random_source));
397 /* Close stdin now, rather than earlier, so that randint_all_new
398 doesn't have to worry about opening something other than
399 stdin. */
400 if (! (echo || input_numbers_option_used (lo_input, hi_input))
401 && (ferror (stdin) || fclose (stdin) != 0))
402 error (EXIT_FAILURE, errno, _("read error"));
404 permutation = randperm_new (randint_source, head_lines, n_lines);
406 if (outfile && ! freopen (outfile, "w", stdout))
407 error (EXIT_FAILURE, errno, "%s", quotearg_colon (outfile));
408 if (write_permuted_output (head_lines, line, lo_input, permutation) != 0)
409 error (EXIT_FAILURE, errno, _("write error"));
411 return EXIT_SUCCESS;