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