Home | History | Annotate | Download | only in posix
      1 /* paste.c - Merge corresponding lines
      2  *
      3  * Copyright 2012 Felix Janda <felix.janda (at) 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 
     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 
    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 
    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