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 : }
|