bdf31ccdeaa656276155c9e01ad7cb26d330ffe2
[platform/upstream/coreutils.git] / src / paste.c
1 /* paste - merge lines of files
2    Copyright (C) 1984, 1997, 1998, 1999 by David M. Ihnat
3
4    This program is a total rewrite of the Bell Laboratories Unix(Tm)
5    command of the same name, as of System V.  It contains no proprietary
6    code, and therefore may be used without violation of any proprietary
7    agreements whatsoever.  However, you will notice that the program is
8    copyrighted by me.  This is to assure the program does *not* fall
9    into the public domain.  Thus, I may specify just what I am now:
10    This program may be freely copied and distributed, provided this notice
11    remains; it may not be sold for profit without express written consent of
12    the author.
13    Please note that I recreated the behavior of the Unix(Tm) 'paste' command
14    as faithfully as possible, with minor exceptions; however,
15    I haven't run a full set of regression tests.  Thus, the user of
16    this program accepts full responsibility for any effects or loss;
17    in particular, the author is not responsible for any losses,
18    explicit or incidental, that may be incurred through use of this program.
19
20    I ask that any bugs (and, if possible, fixes) be reported to me when
21    possible.  -David Ihnat (312) 784-4544 ignatz@homebru.chi.il.us
22
23    The list of valid escape sequences has been expanded over the Unix
24    version, to include \b, \f, \r, and \v.
25
26    POSIX changes, bug fixes, long-named options, and cleanup
27    by David MacKenzie <djm@gnu.ai.mit.edu>.
28
29    Options:
30    --serial
31    -s                           Paste one file at a time rather than
32                                 one line from each file.
33    --delimiters=delim-list
34    -d delim-list                Consecutively use the characters in
35                                 DELIM-LIST instead of tab to separate
36                                 merged lines.  When DELIM-LIST is exhausted,
37                                 start again at its beginning.
38    A FILE of `-' means standard input.
39    If no FILEs are given, standard input is used. */
40
41 #include <config.h>
42
43 #include <stdio.h>
44 #include <getopt.h>
45 #include <sys/types.h>
46 #include "system.h"
47 #include "error.h"
48 #include "long-options.h"
49
50 /* The official name of this program (e.g., no `g' prefix).  */
51 #define PROGRAM_NAME "paste"
52
53 /* Indicates that no delimiter should be added in the current position. */
54 #define EMPTY_DELIM '\0'
55
56 static FILE dummy_closed;
57 /* Element marking a file that has reached EOF and been closed. */
58 #define CLOSED (&dummy_closed)
59
60 static FILE dummy_endlist;
61 /* Element marking end of list of open files. */
62 #define ENDLIST (&dummy_endlist)
63
64 /* Name this program was run with. */
65 char *program_name;
66
67 /* If nonzero, we have read standard input at some point. */
68 static int have_read_stdin;
69
70 /* If nonzero, merge subsequent lines of each file rather than
71    corresponding lines from each file in parallel. */
72 static int serial_merge;
73
74 /* The delimeters between lines of input files (used cyclically). */
75 static char *delims;
76
77 /* A pointer to the character after the end of `delims'. */
78 static char *delim_end;
79
80 static struct option const longopts[] =
81 {
82   {"serial", no_argument, 0, 's'},
83   {"delimiters", required_argument, 0, 'd'},
84   {0, 0, 0, 0}
85 };
86
87 /* Replace backslash representations of special characters in
88    STRPTR with their actual values.
89    The set of possible backslash characters has been expanded beyond
90    that recognized by the Unix version.
91
92    Return a pointer to the character after the new end of STRPTR. */
93
94 static char *
95 collapse_escapes (char *strptr)
96 {
97   register char *strout;
98
99   strout = strptr;              /* Start at the same place, anyway. */
100
101   while (*strptr)
102     {
103       if (*strptr != '\\')      /* Is it an escape character? */
104         *strout++ = *strptr++;  /* No, just transfer it. */
105       else
106         {
107           switch (*++strptr)
108             {
109             case '0':
110               *strout++ = EMPTY_DELIM;
111               break;
112
113             case 'b':
114               *strout++ = '\b';
115               break;
116
117             case 'f':
118               *strout++ = '\f';
119               break;
120
121             case 'n':
122               *strout++ = '\n';
123               break;
124
125             case 'r':
126               *strout++ = '\r';
127               break;
128
129             case 't':
130               *strout++ = '\t';
131               break;
132
133             case 'v':
134               *strout++ = '\v';
135               break;
136
137             default:
138               *strout++ = *strptr;
139               break;
140             }
141           strptr++;
142         }
143     }
144   return strout;
145 }
146
147 /* Perform column paste on the NFILES files named in FNAMPTR.
148    Return 0 if no errors, 1 if one or more files could not be
149    opened or read. */
150
151 static int
152 paste_parallel (int nfiles, char **fnamptr)
153 {
154   int errors = 0;               /* 1 if open or read errors occur. */
155   /* Number of files for which space is allocated in `delbuf' and `fileptr'.
156      Enlarged as necessary. */
157   int file_list_size = 12;
158   int chr;                      /* Input character. */
159   int line_length;              /* Number of chars in line. */
160   int somedone;                 /* 0 if all files empty for this line. */
161   /* If all files are just ready to be closed, or will be on this
162      round, the string of delimiters must be preserved.
163      delbuf[0] through delbuf[file_list_size]
164      store the delimiters for closed files. */
165   char *delbuf;
166   int delims_saved;             /* Number of delims saved in `delbuf'. */
167   register char *delimptr;      /* Cycling pointer into `delims'. */
168   FILE **fileptr;               /* Streams open to the files to process. */
169   int files_open;               /* Number of files still open to process. */
170   int i;                        /* Loop index. */
171   int opened_stdin = 0;         /* Nonzero if any fopen got fd 0. */
172
173 #ifdef lint  /* Suppress `used before initialized' warning.  */
174   chr = 0;
175 #endif
176
177   delbuf = (char *) xmalloc (file_list_size + 2);
178   fileptr = (FILE **) xmalloc ((file_list_size + 1) * sizeof (FILE *));
179
180   /* Attempt to open all files.  This could be expanded to an infinite
181      number of files, but at the (considerable) expense of remembering
182      each file and its current offset, then opening/reading/closing.  */
183
184   for (files_open = 0; files_open < nfiles; ++files_open)
185     {
186       if (files_open == file_list_size - 2)
187         {
188           file_list_size += 12;
189           delbuf = (char *) xrealloc (delbuf, file_list_size + 2);
190           fileptr = (FILE **) xrealloc ((char *) fileptr, (file_list_size + 1)
191                                         * sizeof (FILE *));
192         }
193       if (STREQ (fnamptr[files_open], "-"))
194         {
195           have_read_stdin = 1;
196           fileptr[files_open] = stdin;
197         }
198       else
199         {
200           fileptr[files_open] = fopen (fnamptr[files_open], "r");
201           if (fileptr[files_open] == NULL)
202             error (EXIT_FAILURE, errno, "%s", fnamptr[files_open]);
203           else if (fileno (fileptr[files_open]) == 0)
204             opened_stdin = 1;
205         }
206     }
207
208   fileptr[files_open] = ENDLIST;
209
210   if (opened_stdin && have_read_stdin)
211     error (EXIT_FAILURE, 0, _("standard input is closed"));
212
213   /* Read a line from each file and output it to stdout separated by a
214      delimiter, until we go through the loop without successfully
215      reading from any of the files. */
216
217   while (files_open)
218     {
219       /* Set up for the next line. */
220       somedone = 0;
221       delimptr = delims;
222       delims_saved = 0;
223
224       for (i = 0; fileptr[i] != ENDLIST && files_open; i++)
225         {
226           line_length = 0;      /* Clear so we can easily detect EOF. */
227           if (fileptr[i] != CLOSED)
228             {
229               chr = getc (fileptr[i]);
230               if (chr != EOF && delims_saved)
231                 {
232                   fwrite (delbuf, sizeof (char), delims_saved, stdout);
233                   delims_saved = 0;
234                 }
235
236               while (chr != EOF)
237                 {
238                   line_length++;
239                   if (chr == '\n')
240                     break;
241                   putc (chr, stdout);
242                   chr = getc (fileptr[i]);
243                 }
244             }
245
246           if (line_length == 0)
247             {
248               /* EOF, read error, or closed file.
249                  If an EOF or error, close the file and mark it in the list. */
250               if (fileptr[i] != CLOSED)
251                 {
252                   if (ferror (fileptr[i]))
253                     {
254                       error (0, errno, "%s", fnamptr[i]);
255                       errors = 1;
256                     }
257                   if (fileptr[i] == stdin)
258                     clearerr (fileptr[i]); /* Also clear EOF. */
259                   else if (fclose (fileptr[i]) == EOF)
260                     {
261                       error (0, errno, "%s", fnamptr[i]);
262                       errors = 1;
263                     }
264
265                   fileptr[i] = CLOSED;
266                   files_open--;
267                 }
268
269               if (fileptr[i + 1] == ENDLIST)
270                 {
271                   /* End of this output line.
272                      Is this the end of the whole thing? */
273                   if (somedone)
274                     {
275                       /* No.  Some files were not closed for this line. */
276                       if (delims_saved)
277                         {
278                           fwrite (delbuf, sizeof (char), delims_saved, stdout);
279                           delims_saved = 0;
280                         }
281                       putc ('\n', stdout);
282                     }
283                   continue;     /* Next read of files, or exit. */
284                 }
285               else
286                 {
287                   /* Closed file; add delimiter to `delbuf'. */
288                   if (*delimptr != EMPTY_DELIM)
289                     delbuf[delims_saved++] = *delimptr;
290                   if (++delimptr == delim_end)
291                     delimptr = delims;
292                 }
293             }
294           else
295             {
296               /* Some data read. */
297               somedone++;
298
299               /* Except for last file, replace last newline with delim. */
300               if (fileptr[i + 1] != ENDLIST)
301                 {
302                   if (chr != '\n')
303                     putc (chr, stdout);
304                   if (*delimptr != EMPTY_DELIM)
305                     putc (*delimptr, stdout);
306                   if (++delimptr == delim_end)
307                     delimptr = delims;
308                 }
309               else
310                 putc (chr, stdout);
311             }
312         }
313     }
314   return errors;
315 }
316
317 /* Perform serial paste on the NFILES files named in FNAMPTR.
318    Return 0 if no errors, 1 if one or more files could not be
319    opened or read. */
320
321 static int
322 paste_serial (int nfiles, char **fnamptr)
323 {
324   int errors = 0;               /* 1 if open or read errors occur. */
325   register int charnew, charold; /* Current and previous char read. */
326   register char *delimptr;      /* Current delimiter char. */
327   register FILE *fileptr;       /* Open for reading current file. */
328
329   for (; nfiles; nfiles--, fnamptr++)
330     {
331       if (STREQ (*fnamptr, "-"))
332         {
333           have_read_stdin = 1;
334           fileptr = stdin;
335         }
336       else
337         {
338           fileptr = fopen (*fnamptr, "r");
339           if (fileptr == NULL)
340             {
341               error (0, errno, "%s", *fnamptr);
342               errors = 1;
343               continue;
344             }
345         }
346
347       delimptr = delims;        /* Set up for delimiter string. */
348
349       charold = getc (fileptr);
350       if (charold != EOF)
351         {
352           /* `charold' is set up.  Hit it!
353              Keep reading characters, stashing them in `charnew';
354              output `charold', converting to the appropriate delimiter
355              character if needed.  After the EOF, output `charold'
356              if it's a newline; otherwise, output it and then a newline. */
357
358           while ((charnew = getc (fileptr)) != EOF)
359             {
360               /* Process the old character. */
361               if (charold == '\n')
362                 {
363                   if (*delimptr != EMPTY_DELIM)
364                     putc (*delimptr, stdout);
365
366                   if (++delimptr == delim_end)
367                     delimptr = delims;
368                 }
369               else
370                 putc (charold, stdout);
371
372               charold = charnew;
373             }
374
375           /* Hit EOF.  Process that last character. */
376           putc (charold, stdout);
377         }
378
379       if (charold != '\n')
380         putc ('\n', stdout);
381
382       if (ferror (fileptr))
383         {
384           error (0, errno, "%s", *fnamptr);
385           errors = 1;
386         }
387       if (fileptr == stdin)
388         clearerr (fileptr);     /* Also clear EOF. */
389       else if (fclose (fileptr) == EOF)
390         {
391           error (0, errno, "%s", *fnamptr);
392           errors = 1;
393         }
394     }
395   return errors;
396 }
397
398 void
399 usage (int status)
400 {
401   if (status != 0)
402     fprintf (stderr, _("Try `%s --help' for more information.\n"),
403              program_name);
404   else
405     {
406       printf (_("\
407 Usage: %s [OPTION]... [FILE]...\n\
408 "),
409               program_name);
410       printf (_("\
411 Write lines consisting of the sequentially corresponding lines from\n\
412 each FILE, separated by TABs, to standard output.\n\
413 With no FILE, or when FILE is -, read standard input.\n\
414 \n\
415   -d, --delimiters=LIST   reuse characters from LIST instead of TABs\n\
416   -s, --serial            paste one file at a time instead of in parallel\n\
417       --help              display this help and exit\n\
418       --version           output version information and exit\n\
419 \n\
420 "));
421       puts (_("\nReport bugs to <bug-textutils@gnu.org>."));
422     }
423   exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
424 }
425
426 int
427 main (int argc, char **argv)
428 {
429   int optc, exit_status;
430   char default_delims[2], zero_delims[3];
431
432   program_name = argv[0];
433   setlocale (LC_ALL, "");
434   bindtextdomain (PACKAGE, LOCALEDIR);
435   textdomain (PACKAGE);
436
437   parse_long_options (argc, argv, "paste", GNU_PACKAGE, VERSION,
438                       "David M. Ihnat", usage);
439
440   have_read_stdin = 0;
441   serial_merge = 0;
442   delims = default_delims;
443   strcpy (delims, "\t");
444   strcpy (zero_delims, "\\0");
445
446   while ((optc = getopt_long (argc, argv, "d:s", longopts, NULL)) != -1)
447     {
448       switch (optc)
449         {
450         case 0:
451           break;
452
453         case 'd':
454           /* Delimiter character(s). */
455           if (optarg[0] == '\0')
456             optarg = zero_delims;
457           delims = optarg;
458           break;
459
460         case 's':
461           serial_merge++;
462           break;
463
464         default:
465           usage (1);
466         }
467     }
468
469   if (optind == argc)
470     argv[argc++] = "-";
471
472   delim_end = collapse_escapes (delims);
473
474   if (!serial_merge)
475     exit_status = paste_parallel (argc - optind, &argv[optind]);
476   else
477     exit_status = paste_serial (argc - optind, &argv[optind]);
478   if (have_read_stdin && fclose (stdin) == EOF)
479     error (EXIT_FAILURE, errno, "-");
480   if (ferror (stdout) || fclose (stdout) == EOF)
481     error (EXIT_FAILURE, errno, _("write error"));
482   exit (exit_status == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
483 }