maint: go back to using ‘error’
[coreutils.git] / src / paste.c
blob73ddd1de6310c3f2a9e14095abcad3ee3da86b11
1 /* paste - merge lines of files
2 Copyright (C) 1997-2023 Free Software Foundation, Inc.
3 Copyright (C) 1984 David M. Ihnat
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <https://www.gnu.org/licenses/>. */
18 /* Written by David Ihnat. */
20 /* The list of valid escape sequences has been expanded over the Unix
21 version, to include \b, \f, \r, and \v.
23 POSIX changes, bug fixes, long-named options, and cleanup
24 by David MacKenzie <djm@gnu.ai.mit.edu>.
26 Options:
27 --serial
28 -s Paste one file at a time rather than
29 one line from each file.
30 --delimiters=delim-list
31 -d delim-list Consecutively use the characters in
32 DELIM-LIST instead of tab to separate
33 merged lines. When DELIM-LIST is exhausted,
34 start again at its beginning.
35 A FILE of '-' means standard input.
36 If no FILEs are given, standard input is used. */
38 #include <config.h>
40 #include <stdio.h>
41 #include <getopt.h>
42 #include <sys/types.h>
43 #include "system.h"
44 #include "fadvise.h"
46 /* The official name of this program (e.g., no 'g' prefix). */
47 #define PROGRAM_NAME "paste"
49 #define AUTHORS \
50 proper_name ("David M. Ihnat"), \
51 proper_name ("David MacKenzie")
53 /* Indicates that no delimiter should be added in the current position. */
54 #define EMPTY_DELIM '\0'
56 /* If nonzero, we have read standard input at some point. */
57 static bool have_read_stdin;
59 /* If nonzero, merge subsequent lines of each file rather than
60 corresponding lines from each file in parallel. */
61 static bool serial_merge;
63 /* The delimiters between lines of input files (used cyclically). */
64 static char *delims;
66 /* A pointer to the character after the end of 'delims'. */
67 static char const *delim_end;
69 static unsigned char line_delim = '\n';
71 static struct option const longopts[] =
73 {"serial", no_argument, nullptr, 's'},
74 {"delimiters", required_argument, nullptr, 'd'},
75 {"zero-terminated", no_argument, nullptr, 'z'},
76 {GETOPT_HELP_OPTION_DECL},
77 {GETOPT_VERSION_OPTION_DECL},
78 {nullptr, 0, nullptr, 0}
81 /* Set globals delims and delim_end. Copy STRPTR to DELIMS, converting
82 backslash representations of special characters in STRPTR to their actual
83 values. The set of possible backslash characters has been expanded beyond
84 that recognized by the Unix version.
85 Return 0 upon success.
86 If the string ends in an odd number of backslashes, ignore the
87 final backslash and return nonzero. */
89 static int
90 collapse_escapes (char const *strptr)
92 char *strout = xstrdup (strptr);
93 bool backslash_at_end = false;
95 delims = strout;
97 while (*strptr)
99 if (*strptr != '\\') /* Is it an escape character? */
100 *strout++ = *strptr++; /* No, just transfer it. */
101 else
103 switch (*++strptr)
105 case '0':
106 *strout++ = EMPTY_DELIM;
107 break;
109 case 'b':
110 *strout++ = '\b';
111 break;
113 case 'f':
114 *strout++ = '\f';
115 break;
117 case 'n':
118 *strout++ = '\n';
119 break;
121 case 'r':
122 *strout++ = '\r';
123 break;
125 case 't':
126 *strout++ = '\t';
127 break;
129 case 'v':
130 *strout++ = '\v';
131 break;
133 case '\\':
134 *strout++ = '\\';
135 break;
137 case '\0':
138 backslash_at_end = true;
139 goto done;
141 default:
142 *strout++ = *strptr;
143 break;
145 strptr++;
149 done:
151 delim_end = strout;
152 return backslash_at_end ? 1 : 0;
155 /* Report a write error and exit. */
157 static void
158 write_error (void)
160 error (EXIT_FAILURE, errno, _("write error"));
163 /* Output a single byte, reporting any write errors. */
165 static inline void
166 xputchar (char c)
168 if (putchar (c) < 0)
169 write_error ();
172 /* Perform column paste on the NFILES files named in FNAMPTR.
173 Return true if successful, false if one or more files could not be
174 opened or read. */
176 static bool
177 paste_parallel (size_t nfiles, char **fnamptr)
179 bool ok = true;
180 /* If all files are just ready to be closed, or will be on this
181 round, the string of delimiters must be preserved.
182 delbuf[0] through delbuf[nfiles]
183 store the delimiters for closed files. */
184 char *delbuf = xmalloc (nfiles + 2);
186 /* Streams open to the files to process; null if the corresponding
187 stream is closed. */
188 FILE **fileptr = xnmalloc (nfiles + 1, sizeof *fileptr);
190 /* Number of files still open to process. */
191 size_t files_open;
193 /* True if any fopen got fd == STDIN_FILENO. */
194 bool opened_stdin = false;
196 /* Attempt to open all files. This could be expanded to an infinite
197 number of files, but at the (considerable) expense of remembering
198 each file and its current offset, then opening/reading/closing. */
200 for (files_open = 0; files_open < nfiles; ++files_open)
202 if (STREQ (fnamptr[files_open], "-"))
204 have_read_stdin = true;
205 fileptr[files_open] = stdin;
207 else
209 fileptr[files_open] = fopen (fnamptr[files_open], "r");
210 if (fileptr[files_open] == nullptr)
211 error (EXIT_FAILURE, errno, "%s", quotef (fnamptr[files_open]));
212 else if (fileno (fileptr[files_open]) == STDIN_FILENO)
213 opened_stdin = true;
214 fadvise (fileptr[files_open], FADVISE_SEQUENTIAL);
218 if (opened_stdin && have_read_stdin)
219 error (EXIT_FAILURE, 0, _("standard input is closed"));
221 /* Read a line from each file and output it to stdout separated by a
222 delimiter, until we go through the loop without successfully
223 reading from any of the files. */
225 while (files_open)
227 /* Set up for the next line. */
228 bool somedone = false;
229 char const *delimptr = delims;
230 size_t delims_saved = 0; /* Number of delims saved in 'delbuf'. */
232 for (size_t i = 0; i < nfiles && files_open; i++)
234 int chr; /* Input character. */
235 int err; /* Input errno value. */
236 bool sometodo = false; /* Input chars to process. */
238 if (fileptr[i])
240 chr = getc (fileptr[i]);
241 err = errno;
242 if (chr != EOF && delims_saved)
244 if (fwrite (delbuf, 1, delims_saved, stdout) != delims_saved)
245 write_error ();
246 delims_saved = 0;
249 while (chr != EOF)
251 sometodo = true;
252 if (chr == line_delim)
253 break;
254 xputchar (chr);
255 chr = getc (fileptr[i]);
256 err = errno;
260 if (! sometodo)
262 /* EOF, read error, or closed file.
263 If an EOF or error, close the file. */
264 if (fileptr[i])
266 if (!ferror (fileptr[i]))
267 err = 0;
268 if (fileptr[i] == stdin)
269 clearerr (fileptr[i]); /* Also clear EOF. */
270 else if (fclose (fileptr[i]) == EOF && !err)
271 err = errno;
272 if (err)
274 error (0, err, "%s", quotef (fnamptr[i]));
275 ok = false;
278 fileptr[i] = nullptr;
279 files_open--;
282 if (i + 1 == nfiles)
284 /* End of this output line.
285 Is this the end of the whole thing? */
286 if (somedone)
288 /* No. Some files were not closed for this line. */
289 if (delims_saved)
291 if (fwrite (delbuf, 1, delims_saved, stdout)
292 != delims_saved)
293 write_error ();
294 delims_saved = 0;
296 xputchar (line_delim);
298 continue; /* Next read of files, or exit. */
300 else
302 /* Closed file; add delimiter to 'delbuf'. */
303 if (*delimptr != EMPTY_DELIM)
304 delbuf[delims_saved++] = *delimptr;
305 if (++delimptr == delim_end)
306 delimptr = delims;
309 else
311 /* Some data read. */
312 somedone = true;
314 /* Except for last file, replace last newline with delim. */
315 if (i + 1 != nfiles)
317 if (chr != line_delim && chr != EOF)
318 xputchar (chr);
319 if (*delimptr != EMPTY_DELIM)
320 xputchar (*delimptr);
321 if (++delimptr == delim_end)
322 delimptr = delims;
324 else
326 /* If the last line of the last file lacks a newline,
327 print one anyhow. POSIX requires this. */
328 char c = (chr == EOF ? line_delim : chr);
329 xputchar (c);
334 free (fileptr);
335 free (delbuf);
336 return ok;
339 /* Perform serial paste on the NFILES files named in FNAMPTR.
340 Return true if no errors, false if one or more files could not be
341 opened or read. */
343 static bool
344 paste_serial (size_t nfiles, char **fnamptr)
346 bool ok = true; /* false if open or read errors occur. */
347 int charnew, charold; /* Current and previous char read. */
348 char const *delimptr; /* Current delimiter char. */
349 FILE *fileptr; /* Open for reading current file. */
351 for (; nfiles; nfiles--, fnamptr++)
353 int saved_errno;
354 bool is_stdin = STREQ (*fnamptr, "-");
355 if (is_stdin)
357 have_read_stdin = true;
358 fileptr = stdin;
360 else
362 fileptr = fopen (*fnamptr, "r");
363 if (fileptr == nullptr)
365 error (0, errno, "%s", quotef (*fnamptr));
366 ok = false;
367 continue;
369 fadvise (fileptr, FADVISE_SEQUENTIAL);
372 delimptr = delims; /* Set up for delimiter string. */
374 charold = getc (fileptr);
375 saved_errno = errno;
376 if (charold != EOF)
378 /* 'charold' is set up. Hit it!
379 Keep reading characters, stashing them in 'charnew';
380 output 'charold', converting to the appropriate delimiter
381 character if needed. After the EOF, output 'charold'
382 if it's a newline; otherwise, output it and then a newline. */
384 while ((charnew = getc (fileptr)) != EOF)
386 /* Process the old character. */
387 if (charold == line_delim)
389 if (*delimptr != EMPTY_DELIM)
390 xputchar (*delimptr);
392 if (++delimptr == delim_end)
393 delimptr = delims;
395 else
396 xputchar (charold);
398 charold = charnew;
400 saved_errno = errno;
402 /* Hit EOF. Process that last character. */
403 xputchar (charold);
406 if (charold != line_delim)
407 xputchar (line_delim);
409 if (!ferror (fileptr))
410 saved_errno = 0;
411 if (is_stdin)
412 clearerr (fileptr); /* Also clear EOF. */
413 else if (fclose (fileptr) != 0 && !saved_errno)
414 saved_errno = errno;
415 if (saved_errno)
417 error (0, saved_errno, "%s", quotef (*fnamptr));
418 ok = false;
421 return ok;
424 void
425 usage (int status)
427 if (status != EXIT_SUCCESS)
428 emit_try_help ();
429 else
431 printf (_("\
432 Usage: %s [OPTION]... [FILE]...\n\
434 program_name);
435 fputs (_("\
436 Write lines consisting of the sequentially corresponding lines from\n\
437 each FILE, separated by TABs, to standard output.\n\
438 "), stdout);
440 emit_stdin_note ();
441 emit_mandatory_arg_note ();
443 fputs (_("\
444 -d, --delimiters=LIST reuse characters from LIST instead of TABs\n\
445 -s, --serial paste one file at a time instead of in parallel\n\
446 "), stdout);
447 fputs (_("\
448 -z, --zero-terminated line delimiter is NUL, not newline\n\
449 "), stdout);
450 fputs (HELP_OPTION_DESCRIPTION, stdout);
451 fputs (VERSION_OPTION_DESCRIPTION, stdout);
452 /* FIXME: add a couple of examples. */
453 emit_ancillary_info (PROGRAM_NAME);
455 exit (status);
459 main (int argc, char **argv)
461 int optc;
462 char const *delim_arg = "\t";
464 initialize_main (&argc, &argv);
465 set_program_name (argv[0]);
466 setlocale (LC_ALL, "");
467 bindtextdomain (PACKAGE, LOCALEDIR);
468 textdomain (PACKAGE);
470 atexit (close_stdout);
472 have_read_stdin = false;
473 serial_merge = false;
475 while ((optc = getopt_long (argc, argv, "d:sz", longopts, nullptr)) != -1)
477 switch (optc)
479 case 'd':
480 /* Delimiter character(s). */
481 delim_arg = (optarg[0] == '\0' ? "\\0" : optarg);
482 break;
484 case 's':
485 serial_merge = true;
486 break;
488 case 'z':
489 line_delim = '\0';
490 break;
492 case_GETOPT_HELP_CHAR;
494 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
496 default:
497 usage (EXIT_FAILURE);
501 int nfiles = argc - optind;
502 if (nfiles == 0)
504 argv[optind] = bad_cast ("-");
505 nfiles++;
508 if (collapse_escapes (delim_arg))
510 /* Don't use the quote() quoting style, because that would double the
511 number of displayed backslashes, making the diagnostic look bogus. */
512 error (EXIT_FAILURE, 0,
513 _("delimiter list ends with an unescaped backslash: %s"),
514 quotearg_n_style_colon (0, c_maybe_quoting_style, delim_arg));
517 bool ok = ((serial_merge ? paste_serial : paste_parallel)
518 (nfiles, &argv[optind]));
520 free (delims);
522 if (have_read_stdin && fclose (stdin) == EOF)
523 error (EXIT_FAILURE, errno, "-");
524 return ok ? EXIT_SUCCESS : EXIT_FAILURE;