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