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
9 USE_PASTE(NEWTOY(paste, "d:s", TOYFLAG_BIN|TOYFLAG_LOCALE))
10
11 config 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
GLOBALS(char * d;int files;)26 GLOBALS(
27 char *d;
28
29 int files;
30 )
31
32 // \0 is weird, and -d "" is also weird.
33
34 static 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 mbtowc(0, 0, 0);
46
47 for (i = any = dcount = dlen = 0; seq || i<TT.files; i++) {
48 size_t blen;
49 wchar_t wc;
50 FILE *ff = seq ? *fps : fps[i];
51
52 // Read and output line, preserving embedded NUL bytes.
53
54 buf = 0;
55 len = 0;
56 if (!ff || 0>=(len = getline(&buf, &blen, ff))) {
57 if (ff && ff!=stdin) fclose(ff);
58 if (seq) return;
59 fps[i] = 0;
60 if (!any) continue;
61 }
62 dcount = any ? 1 : i;
63 any = 1;
64
65 // Output delimiters as necessary: not at beginning/end of line,
66 // catch up if first few files had no input but a later one did.
67 // Entire line with no input means no output.
68
69 while (dcount) {
70
71 // Find next delimiter, which can be "", \n, or UTF8 w/combining chars
72 dstr = dpos;
73 dlen = 0;
74 dcount--;
75
76 if (!*TT.d) {;}
77 else if (*dpos == '\\') {
78 if (*++dpos=='0') dpos++;
79 else {
80 dlen = 1;
81 if ((c = unescape(*dpos))) {
82 dstr = &c;
83 dpos++;
84 }
85 }
86 } else {
87 while (0<(dlen = mbtowc(&wc, dpos, 99))) {
88 dpos += dlen;
89 if (!(dlen = wcwidth(wc))) continue;
90 if (dlen<0) dpos = dstr+1;
91 break;
92 }
93 dlen = dpos-dstr;
94 }
95 if (!*dpos) dpos = TT.d;
96
97 if (dlen) fwrite(dstr, dlen, 1, stdout);
98 }
99
100 if (0<len) {
101 fwrite(buf, len-(buf[len-1]=='\n'), 1, stdout);
102 free(buf);
103 }
104 }
105
106 // Only need a newline if we output something
107 if (any) xputc('\n');
108 else break;
109 }
110 }
111
do_paste(int fd,char * name)112 static void do_paste(int fd, char *name)
113 {
114 FILE **fps = (void *)toybuf;
115
116 if (!(fps[TT.files++] = (fd ? fdopen(fd, "r") : stdin))) perror_exit(0);
117 if (TT.files >= sizeof(toybuf)/sizeof(FILE *)) perror_exit("tilt");
118 if (toys.optflags&FLAG_s) {
119 paste_files();
120 xputc('\n');
121 TT.files = 0;
122 }
123 }
124
paste_main(void)125 void paste_main(void)
126 {
127 if (!(toys.optflags&FLAG_d)) TT.d = "\t";
128
129 loopfiles_rw(toys.optargs, O_RDONLY, 0, do_paste);
130 if (!(toys.optflags&FLAG_s)) paste_files();
131 }
132