toybox/toys/posix/paste.c
<<
>>
Prefs
   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