paste.c 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. /* paste.c - Merge corresponding lines
  2. *
  3. * Copyright 2012 Felix Janda <felix.janda@posteo.de>
  4. *
  5. * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/paste.html
  6. *
  7. * Deviations from posix: the FILE argument isn't mandatory, none == '-'
  8. USE_PASTE(NEWTOY(paste, "d:s", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
  9. config PASTE
  10. bool "paste"
  11. default y
  12. help
  13. usage: paste [-s] [-d DELIMITERS] [FILE...]
  14. Merge corresponding lines from each input file.
  15. -d List of delimiter characters to separate fields with (default is \t)
  16. -s Sequential mode: turn each input file into one line of output
  17. */
  18. #define FOR_paste
  19. #include "toys.h"
  20. GLOBALS(
  21. char *d;
  22. int files;
  23. )
  24. // \0 is weird, and -d "" is also weird.
  25. static void paste_files(void)
  26. {
  27. FILE **fps = (void *)toybuf;
  28. char *dpos, *dstr, *buf, c;
  29. int i, any, dcount, dlen, len, seq = toys.optflags&FLAG_s;
  30. // Loop through lines until no input left
  31. for (;;) {
  32. // Start of each line/file resets delimiter cycle
  33. dpos = TT.d;
  34. for (i = any = dcount = dlen = 0; seq || i<TT.files; i++) {
  35. size_t blen;
  36. unsigned wc;
  37. FILE *ff = seq ? *fps : fps[i];
  38. // Read and output line, preserving embedded NUL bytes.
  39. buf = 0;
  40. len = 0;
  41. if (!ff || 0>=(len = getline(&buf, &blen, ff))) {
  42. if (ff && ff!=stdin) fclose(ff);
  43. if (seq) return;
  44. fps[i] = 0;
  45. if (!any) continue;
  46. }
  47. dcount = any ? 1 : i;
  48. any = 1;
  49. // Output delimiters as necessary: not at beginning/end of line,
  50. // catch up if first few files had no input but a later one did.
  51. // Entire line with no input means no output.
  52. while (dcount) {
  53. // Find next delimiter, which can be "", \n, or UTF8 w/combining chars
  54. dstr = dpos;
  55. dlen = 0;
  56. dcount--;
  57. if (!*TT.d) {;}
  58. else if (*dpos == '\\') {
  59. if (*++dpos=='0') dpos++;
  60. else {
  61. dlen = 1;
  62. if ((c = unescape(*dpos))) {
  63. dstr = &c;
  64. dpos++;
  65. }
  66. }
  67. } else {
  68. while (0<(dlen = utf8towc(&wc, dpos, 99))) {
  69. dpos += dlen;
  70. if (!(dlen = wcwidth(wc))) continue;
  71. if (dlen<0) dpos = dstr+1;
  72. break;
  73. }
  74. dlen = dpos-dstr;
  75. }
  76. if (!*dpos) dpos = TT.d;
  77. if (dlen) fwrite(dstr, dlen, 1, stdout);
  78. }
  79. if (0<len) {
  80. fwrite(buf, len-(buf[len-1]=='\n'), 1, stdout);
  81. free(buf);
  82. }
  83. }
  84. // Only need a newline if we output something
  85. if (any) xputc('\n');
  86. else break;
  87. }
  88. }
  89. static void do_paste(int fd, char *name)
  90. {
  91. FILE **fps = (void *)toybuf;
  92. if (!(fps[TT.files++] = (fd ? fdopen(fd, "r") : stdin))) perror_exit(0);
  93. if (TT.files >= sizeof(toybuf)/sizeof(FILE *)) perror_exit("tilt");
  94. if (toys.optflags&FLAG_s) {
  95. paste_files();
  96. xputc('\n');
  97. TT.files = 0;
  98. }
  99. }
  100. void paste_main(void)
  101. {
  102. if (!(toys.optflags&FLAG_d)) TT.d = "\t";
  103. loopfiles_rw(toys.optargs, O_RDONLY, 0, do_paste);
  104. if (!(toys.optflags&FLAG_s)) paste_files();
  105. }