logo

utils-std

Collection of commonly available Unix tools git clone https://anongit.hacktivis.me/git/utils-std.git/

shuf.c (4025B)


  1. // utils-std: Collection of commonly available Unix tools
  2. // SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
  3. // SPDX-License-Identifier: MPL-2.0
  4. #define _POSIX_C_SOURCE 200809L
  5. #include "../libutils/getopt_nolong.h"
  6. #include <errno.h>
  7. #include <stdbool.h>
  8. #include <stdio.h> // getdelim, fprintf
  9. #include <stdlib.h> // free, malloc, srand, rand, exit, strtoul
  10. #include <string.h> // strerror, memcpy
  11. #include <time.h> // time
  12. #include <unistd.h> // getopt
  13. // Not a full shuffle, if there is more than 512 lines then last lines are never going to be printed first.
  14. // But this allows bounded memory usage.
  15. // FIXME: handle newline-less lines
  16. const char *argv0 = "shuf";
  17. #define LINES_LEN 512
  18. static char *lines[LINES_LEN];
  19. static char delim = '\n';
  20. char *line = NULL;
  21. size_t line_len = 0;
  22. unsigned long wrote = 0;
  23. unsigned long write_limit = 0;
  24. static int
  25. shuf(FILE *in, const char *fname)
  26. {
  27. for(int ln = 0;; ln++)
  28. {
  29. errno = 0;
  30. ssize_t nread = getdelim(&line, &line_len, delim, in);
  31. if(errno != 0)
  32. {
  33. fprintf(stderr,
  34. "%s: error: Failed reading line %d from file \"%s\": %s\n",
  35. argv0,
  36. ln,
  37. fname,
  38. strerror(errno));
  39. return 1;
  40. }
  41. if(nread <= 0) return 0;
  42. errno = 0;
  43. char *dup = malloc(nread + 1);
  44. if(!dup)
  45. {
  46. fprintf(
  47. stderr,
  48. "%s: error: Failed to allocate %zd bytes of memory for line %d from file \"%s\": %s\n",
  49. argv0,
  50. nread,
  51. ln,
  52. fname,
  53. strerror(errno));
  54. return 1;
  55. }
  56. memcpy(dup, line, nread);
  57. dup[nread] = '\0';
  58. int p = rand() % LINES_LEN;
  59. if(lines[p] != NULL)
  60. {
  61. fputs(lines[p], stdout);
  62. free(lines[p]);
  63. lines[p] = NULL;
  64. wrote++;
  65. if(write_limit != 0 && write_limit <= wrote) exit(0);
  66. }
  67. lines[p] = dup;
  68. }
  69. }
  70. static void
  71. usage(void)
  72. {
  73. fputs("\
  74. Usage: shuf [-z] [-n num] [files...]\n\
  75. shuf -e [-z] [-n num] [string...]\n\
  76. ",
  77. stderr);
  78. }
  79. int
  80. main(int argc, char *argv[])
  81. {
  82. bool e_flag = false;
  83. srand((int)time(NULL));
  84. for(int c = -1; (c = getopt_nolong(argc, argv, ":en:z")) != -1;)
  85. {
  86. char *endptr = NULL;
  87. switch(c)
  88. {
  89. case 'e':
  90. e_flag = true;
  91. break;
  92. case 'n':
  93. write_limit = strtoul(optarg, &endptr, 0);
  94. if(errno != 0)
  95. {
  96. fprintf(stderr,
  97. "%s: error: Failed parsing number for `-n %s`: %s\n",
  98. argv0,
  99. optarg,
  100. strerror(errno));
  101. return 1;
  102. }
  103. if(endptr != NULL && *endptr != 0)
  104. {
  105. fprintf(stderr,
  106. "%s: error: Found extraneous characters while parsing `-n %s` as a number: %s\n",
  107. argv0,
  108. optarg,
  109. endptr);
  110. return 1;
  111. }
  112. break;
  113. case 'z':
  114. delim = '\0';
  115. break;
  116. case ':':
  117. fprintf(stderr, "%s: error: Missing operand for option: '-%c'\n", argv0, optopt);
  118. usage();
  119. return 1;
  120. case '?':
  121. GETOPT_UNKNOWN_OPT
  122. usage();
  123. return 1;
  124. }
  125. }
  126. argc -= optind;
  127. argv += optind;
  128. if(e_flag)
  129. {
  130. // Fisher-Yates shuffles
  131. for(int i = 0; i <= argc - 2; i++)
  132. {
  133. int p = rand() % argc;
  134. // swap
  135. char *tmp = argv[p];
  136. argv[p] = argv[argc - 1];
  137. argv[argc - 1] = tmp;
  138. }
  139. unsigned long limit = argc;
  140. if(write_limit != 0 && write_limit < limit) limit = write_limit;
  141. for(unsigned long i = 0; i < limit; i++)
  142. {
  143. printf("%s%c", argv[i], delim);
  144. }
  145. return 0;
  146. }
  147. for(int i = 0; i < LINES_LEN; i++)
  148. lines[i] = NULL;
  149. if(argc <= 0)
  150. {
  151. if(shuf(stdin, "<stdin>") != 0) return 1;
  152. }
  153. else
  154. {
  155. for(int i = 0; i < argc; i++)
  156. {
  157. if(strncmp(argv[i], "-", 2) == 0)
  158. {
  159. if(shuf(stdin, "<stdin>") != 0) return 1;
  160. continue;
  161. }
  162. FILE *in = fopen(argv[i], "r");
  163. if(shuf(in, argv[i]) != 0)
  164. {
  165. fclose(in);
  166. return 1;
  167. }
  168. fclose(in);
  169. }
  170. }
  171. // inserts are random so iterating on it is fine
  172. for(int i = 0; i < LINES_LEN; i++)
  173. {
  174. if(write_limit != 0 && write_limit <= wrote) break;
  175. if(lines[i] != NULL)
  176. {
  177. fputs(lines[i], stdout);
  178. free(lines[i]);
  179. wrote++;
  180. }
  181. }
  182. return 0;
  183. }