logo

utils-std

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

paste.c (7458B)


  1. /*-
  2. * SPDX-License-Identifier: BSD-3-Clause
  3. *
  4. * Copyright (c) 1989, 1993
  5. * The Regents of the University of California. All rights reserved.
  6. *
  7. * This code is derived from software contributed to Berkeley by
  8. * Adam S. Moskowitz of Menlo Consulting.
  9. *
  10. * Redistribution and use in source and binary forms, with or without
  11. * modification, are permitted provided that the following conditions
  12. * are met:
  13. * 1. Redistributions of source code must retain the above copyright
  14. * notice, this list of conditions and the following disclaimer.
  15. * 2. Redistributions in binary form must reproduce the above copyright
  16. * notice, this list of conditions and the following disclaimer in the
  17. * documentation and/or other materials provided with the distribution.
  18. * 3. Neither the name of the University nor the names of its contributors
  19. * may be used to endorse or promote products derived from this software
  20. * without specific prior written permission.
  21. *
  22. * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
  23. * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  24. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  25. * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
  26. * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  27. * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  28. * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  29. * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  30. * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  31. * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  32. * SUCH DAMAGE.
  33. */
  34. #define _POSIX_C_SOURCE 200809L
  35. #include "../config.h"
  36. #include "../libutils/err.h"
  37. #include "../libutils/getopt_nolong.h"
  38. #include <assert.h>
  39. #include <errno.h>
  40. #include <limits.h>
  41. #include <locale.h>
  42. #include <stdint.h> // SIZE_MAX
  43. #include <stdio.h>
  44. #include <stdlib.h>
  45. #include <string.h>
  46. #include <sys/types.h>
  47. #include <unistd.h>
  48. #include <wchar.h>
  49. #ifdef HAS_GETOPT_LONG
  50. #include <getopt.h>
  51. #endif
  52. static wchar_t *delim;
  53. static int delimcnt;
  54. static wint_t linedelim = L'\n';
  55. static wchar_t tab[] = L"\t";
  56. const char *argv0 = "paste";
  57. typedef struct _list
  58. {
  59. struct _list *next;
  60. FILE *fp;
  61. int cnt;
  62. char *name;
  63. } LIST;
  64. static int
  65. parallel(char **argv)
  66. {
  67. LIST *lp;
  68. int cnt;
  69. wint_t ich;
  70. wchar_t ch;
  71. char *p;
  72. LIST *head, *tmp;
  73. int opencnt, output;
  74. int err = 0;
  75. for(cnt = 0, head = tmp = NULL; (p = *argv); ++argv, ++cnt)
  76. {
  77. if((lp = malloc(sizeof(LIST))) == NULL) utils_err(1, NULL);
  78. if(p[0] == '-' && !p[1])
  79. lp->fp = stdin;
  80. else
  81. {
  82. lp->fp = fopen(p, "r");
  83. if(!lp->fp) utils_err(1, "%s", p);
  84. assert(lp->fp != stdin);
  85. }
  86. lp->next = NULL;
  87. lp->cnt = cnt;
  88. lp->name = p;
  89. if(!head)
  90. head = tmp = lp;
  91. else
  92. {
  93. tmp->next = lp;
  94. tmp = lp;
  95. }
  96. }
  97. for(opencnt = cnt; opencnt;)
  98. {
  99. for(output = 0, lp = head; lp; lp = lp->next)
  100. {
  101. if(!lp->fp)
  102. {
  103. if(output && lp->cnt && (ch = delim[(lp->cnt - 1) % delimcnt])) putwchar(ch);
  104. continue;
  105. }
  106. if((ich = getwc(lp->fp)) == WEOF)
  107. {
  108. if(!--opencnt) break;
  109. if(lp->fp && lp->fp != stdin)
  110. {
  111. if(fclose(lp->fp) != 0)
  112. {
  113. fprintf(stderr,
  114. "%s: error: Failed closing file '%s': %s\n",
  115. argv0,
  116. lp->name,
  117. strerror(errno));
  118. err = 1;
  119. }
  120. }
  121. lp->fp = NULL;
  122. if(output && lp->cnt && (ch = delim[(lp->cnt - 1) % delimcnt])) putwchar(ch);
  123. continue;
  124. }
  125. /*
  126. * make sure that we don't print any delimiters
  127. * unless there's a non-empty file.
  128. */
  129. if(!output)
  130. {
  131. output = 1;
  132. for(cnt = 0; cnt < lp->cnt; ++cnt)
  133. if((ch = delim[cnt % delimcnt])) putwchar(ch);
  134. }
  135. else if((ch = delim[(lp->cnt - 1) % delimcnt]))
  136. putwchar(ch);
  137. if(ich == linedelim) continue;
  138. do
  139. {
  140. putwchar(ich);
  141. } while((ich = getwc(lp->fp)) != WEOF && ich != linedelim);
  142. }
  143. if(output) putwchar(linedelim);
  144. }
  145. for(lp = head; lp;)
  146. {
  147. if(lp->fp && lp->fp != stdin)
  148. {
  149. if(fclose(lp->fp) != 0)
  150. {
  151. fprintf(
  152. stderr, "%s: error: Failed closing file '%s': %s\n", argv0, lp->name, strerror(errno));
  153. err = 1;
  154. }
  155. }
  156. LIST *next = lp->next;
  157. free(lp);
  158. lp = next;
  159. }
  160. return err;
  161. }
  162. static int
  163. sequential(char **argv)
  164. {
  165. FILE *fp;
  166. int cnt, failed, needdelim;
  167. wint_t ch;
  168. char *p;
  169. failed = 0;
  170. for(; (p = *argv); ++argv)
  171. {
  172. if(p[0] == '-' && !p[1])
  173. fp = stdin;
  174. else if(!(fp = fopen(p, "r")))
  175. {
  176. utils_warn("%s", p);
  177. failed = 1;
  178. continue;
  179. }
  180. cnt = needdelim = 0;
  181. while((ch = getwc(fp)) != WEOF)
  182. {
  183. if(needdelim)
  184. {
  185. needdelim = 0;
  186. if(delim[cnt] != '\0') putwchar(delim[cnt]);
  187. if(++cnt == delimcnt) cnt = 0;
  188. }
  189. if(ch != linedelim)
  190. putwchar(ch);
  191. else
  192. needdelim = 1;
  193. }
  194. if(needdelim) putwchar(linedelim);
  195. if(fp != stdin)
  196. {
  197. if(fclose(fp) != 0)
  198. {
  199. fprintf(stderr, "%s: error: Failed closing file '%s': %s\n", argv0, p, strerror(errno));
  200. failed = 1;
  201. }
  202. }
  203. }
  204. return (failed != 0);
  205. }
  206. static int
  207. tr(wchar_t *arg)
  208. {
  209. int cnt;
  210. wchar_t ch, *p;
  211. for(p = arg, cnt = 0; (ch = *p++); ++arg, ++cnt)
  212. if(ch == '\\') switch(ch = *p++)
  213. {
  214. case 'n':
  215. *arg = '\n';
  216. break;
  217. case 't':
  218. *arg = '\t';
  219. break;
  220. case '0':
  221. *arg = '\0';
  222. break;
  223. default:
  224. *arg = ch;
  225. break;
  226. }
  227. else
  228. *arg = ch;
  229. if(!cnt) utils_errx(1, "no delimiters specified");
  230. return (cnt);
  231. }
  232. static void
  233. usage(void)
  234. {
  235. (void)fprintf(stderr, "usage: paste [-sz] [-d delimiters] [file...]\n");
  236. exit(1);
  237. }
  238. int
  239. main(int argc, char *argv[])
  240. {
  241. char *lc_all = setlocale(LC_ALL, "");
  242. if(lc_all == NULL)
  243. {
  244. fprintf(stderr,
  245. "%s: warning: Failed loading locales. setlocale(LC_ALL, \"\"): %s\n",
  246. argv0,
  247. strerror(errno));
  248. }
  249. errno = 0;
  250. int seq = 0;
  251. #ifdef HAS_GETOPT_LONG
  252. // Strictly for GNUisms compatibility so no long-only options
  253. // clang-format off
  254. static struct option opts[] = {
  255. {"delimiter", required_argument, NULL, 'd'},
  256. {"serial", no_argument, NULL, 's'},
  257. {"zero-terminated", no_argument, NULL, 'z'},
  258. {0, 0, 0, 0},
  259. };
  260. // clang-format on
  261. // Need + as first character to get POSIX-style option parsing
  262. for(int c = -1; (c = getopt_long(argc, argv, "+:d:sz", opts, NULL)) != -1;)
  263. #else
  264. for(int c = -1; (c = getopt_nolong(argc, argv, ":d:sz")) != -1;)
  265. #endif
  266. {
  267. switch(c)
  268. {
  269. case 'd':
  270. {
  271. const char *arg = optarg;
  272. size_t len = mbsrtowcs(NULL, &arg, 0, NULL);
  273. if(len == (size_t)-1) utils_err(1, "delimiters");
  274. if(len == SIZE_MAX) utils_err(1, NULL);
  275. wchar_t *warg = calloc((len + 1), sizeof(*warg));
  276. if(warg == NULL) utils_err(1, NULL);
  277. arg = optarg;
  278. len = mbsrtowcs(warg, &arg, len + 1, NULL);
  279. if(len == (size_t)-1) utils_err(1, "delimiters");
  280. delimcnt = tr(delim = warg);
  281. break;
  282. }
  283. case 's':
  284. seq = 1;
  285. break;
  286. case 'z':
  287. linedelim = L'\0';
  288. break;
  289. case ':':
  290. fprintf(stderr, "%s: error: Missing operand for option: '-%c'\n", argv0, optopt);
  291. usage();
  292. return 1;
  293. case '?':
  294. GETOPT_UNKNOWN_OPT
  295. usage();
  296. return 1;
  297. default:
  298. abort();
  299. }
  300. }
  301. argc -= optind;
  302. argv += optind;
  303. char *dash[] = {(char *)"-", NULL};
  304. char **args = argc > 0 ? argv : dash;
  305. if(!delim)
  306. {
  307. delimcnt = 1;
  308. delim = tab;
  309. }
  310. if(seq)
  311. return sequential(args);
  312. else
  313. return parallel(args);
  314. }