source: trunk/coreutils/src/paste.c@ 2603

Last change on this file since 2603 was 2554, checked in by bird, 20 years ago

coretuils-5.94

File size: 12.0 KB
Line 
1/* paste - merge lines of files
2 Copyright (C) 1997-2005 Free Software Foundation, Inc.
3 Copyright (C) 1984 David M. Ihnat
4
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 2, or (at your option)
8 any later version.
9
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.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software Foundation,
17 Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
18
19/* Written by David Ihnat. */
20
21/* The list of valid escape sequences has been expanded over the Unix
22 version, to include \b, \f, \r, and \v.
23
24 POSIX changes, bug fixes, long-named options, and cleanup
25 by David MacKenzie <[email protected]>.
26
27 Options:
28 --serial
29 -s Paste one file at a time rather than
30 one line from each file.
31 --delimiters=delim-list
32 -d delim-list Consecutively use the characters in
33 DELIM-LIST instead of tab to separate
34 merged lines. When DELIM-LIST is exhausted,
35 start again at its beginning.
36 A FILE of `-' means standard input.
37 If no FILEs are given, standard input is used. */
38
39#include <config.h>
40
41#include <stdio.h>
42#include <getopt.h>
43#include <sys/types.h>
44#include "system.h"
45#include "error.h"
46
47/* The official name of this program (e.g., no `g' prefix). */
48#define PROGRAM_NAME "paste"
49
50#define AUTHORS "David M. Ihnat", "David MacKenzie"
51
52/* Indicates that no delimiter should be added in the current position. */
53#define EMPTY_DELIM '\0'
54
55/* Name this program was run with. */
56char *program_name;
57
58/* If nonzero, we have read standard input at some point. */
59static bool have_read_stdin;
60
61/* If nonzero, merge subsequent lines of each file rather than
62 corresponding lines from each file in parallel. */
63static bool serial_merge;
64
65/* The delimeters between lines of input files (used cyclically). */
66static char *delims;
67
68/* A pointer to the character after the end of `delims'. */
69static char const *delim_end;
70
71static struct option const longopts[] =
72{
73 {"serial", no_argument, NULL, 's'},
74 {"delimiters", required_argument, NULL, 'd'},
75 {GETOPT_HELP_OPTION_DECL},
76 {GETOPT_VERSION_OPTION_DECL},
77 {NULL, 0, NULL, 0}
78};
79
80/* Set globals delims and delim_end. Copy STRPTR to DELIMS, converting
81 backslash representations of special characters in STRPTR to their actual
82 values. The set of possible backslash characters has been expanded beyond
83 that recognized by the Unix version. */
84
85static void
86collapse_escapes (char const *strptr)
87{
88 char *strout = xstrdup (strptr);
89 delims = strout;
90
91 while (*strptr)
92 {
93 if (*strptr != '\\') /* Is it an escape character? */
94 *strout++ = *strptr++; /* No, just transfer it. */
95 else
96 {
97 switch (*++strptr)
98 {
99 case '0':
100 *strout++ = EMPTY_DELIM;
101 break;
102
103 case 'b':
104 *strout++ = '\b';
105 break;
106
107 case 'f':
108 *strout++ = '\f';
109 break;
110
111 case 'n':
112 *strout++ = '\n';
113 break;
114
115 case 'r':
116 *strout++ = '\r';
117 break;
118
119 case 't':
120 *strout++ = '\t';
121 break;
122
123 case 'v':
124 *strout++ = '\v';
125 break;
126
127 default:
128 *strout++ = *strptr;
129 break;
130 }
131 strptr++;
132 }
133 }
134 delim_end = strout;
135}
136
137/* Report a write error and exit. */
138
139static void write_error (void) ATTRIBUTE_NORETURN;
140static void
141write_error (void)
142{
143 error (EXIT_FAILURE, errno, _("write error"));
144 abort ();
145}
146
147/* Output a single byte, reporting any write errors. */
148
149static inline void
150xputchar (char c)
151{
152 if (putchar (c) < 0)
153 write_error ();
154}
155
156/* Perform column paste on the NFILES files named in FNAMPTR.
157 Return true if successful, false if one or more files could not be
158 opened or read. */
159
160static bool
161paste_parallel (size_t nfiles, char **fnamptr)
162{
163 bool ok = true;
164 /* If all files are just ready to be closed, or will be on this
165 round, the string of delimiters must be preserved.
166 delbuf[0] through delbuf[nfiles]
167 store the delimiters for closed files. */
168 char *delbuf = xmalloc (nfiles + 2);
169
170 /* Streams open to the files to process; NULL if the corresponding
171 stream is closed. */
172 FILE **fileptr = xnmalloc (nfiles + 1, sizeof *fileptr);
173
174 /* Number of files still open to process. */
175 size_t files_open;
176
177 /* True if any fopen got fd == STDIN_FILENO. */
178 bool opened_stdin = false;
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 (STREQ (fnamptr[files_open], "-"))
187 {
188 have_read_stdin = true;
189 fileptr[files_open] = stdin;
190 }
191 else
192 {
193 fileptr[files_open] = fopen (fnamptr[files_open], "r");
194 if (fileptr[files_open] == NULL)
195 error (EXIT_FAILURE, errno, "%s", fnamptr[files_open]);
196 else if (fileno (fileptr[files_open]) == STDIN_FILENO)
197 opened_stdin = true;
198 }
199 }
200
201 if (opened_stdin && have_read_stdin)
202 error (EXIT_FAILURE, 0, _("standard input is closed"));
203
204 /* Read a line from each file and output it to stdout separated by a
205 delimiter, until we go through the loop without successfully
206 reading from any of the files. */
207
208 while (files_open)
209 {
210 /* Set up for the next line. */
211 bool somedone = false;
212 char const *delimptr = delims;
213 size_t delims_saved = 0; /* Number of delims saved in `delbuf'. */
214 size_t i;
215
216 for (i = 0; i < nfiles && files_open; i++)
217 {
218 int chr IF_LINT (= 0); /* Input character. */
219 int err IF_LINT (= 0); /* Input errno value. */
220 size_t line_length = 0; /* Number of chars in line. */
221
222 if (fileptr[i])
223 {
224 chr = getc (fileptr[i]);
225 err = errno;
226 if (chr != EOF && delims_saved)
227 {
228 if (fwrite (delbuf, 1, delims_saved, stdout) != delims_saved)
229 write_error ();
230 delims_saved = 0;
231 }
232
233 while (chr != EOF)
234 {
235 line_length++;
236 if (chr == '\n')
237 break;
238 xputchar (chr);
239 chr = getc (fileptr[i]);
240 err = errno;
241 }
242 }
243
244 if (line_length == 0)
245 {
246 /* EOF, read error, or closed file.
247 If an EOF or error, close the file. */
248 if (fileptr[i])
249 {
250 if (ferror (fileptr[i]))
251 {
252 error (0, err, "%s", fnamptr[i]);
253 ok = false;
254 }
255 if (fileptr[i] == stdin)
256 clearerr (fileptr[i]); /* Also clear EOF. */
257 else if (fclose (fileptr[i]) == EOF)
258 {
259 error (0, errno, "%s", fnamptr[i]);
260 ok = false;
261 }
262
263 fileptr[i] = NULL;
264 files_open--;
265 }
266
267 if (i + 1 == nfiles)
268 {
269 /* End of this output line.
270 Is this the end of the whole thing? */
271 if (somedone)
272 {
273 /* No. Some files were not closed for this line. */
274 if (delims_saved)
275 {
276 if (fwrite (delbuf, 1, delims_saved, stdout)
277 != delims_saved)
278 write_error ();
279 delims_saved = 0;
280 }
281 xputchar ('\n');
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 = true;
298
299 /* Except for last file, replace last newline with delim. */
300 if (i + 1 != nfiles)
301 {
302 if (chr != '\n' && chr != EOF)
303 xputchar (chr);
304 if (*delimptr != EMPTY_DELIM)
305 xputchar (*delimptr);
306 if (++delimptr == delim_end)
307 delimptr = delims;
308 }
309 else
310 {
311 /* If the last line of the last file lacks a newline,
312 print one anyhow. POSIX requires this. */
313 char c = (chr == EOF ? '\n' : chr);
314 xputchar (c);
315 }
316 }
317 }
318 }
319 free (fileptr);
320 free (delbuf);
321 return ok;
322}
323
324/* Perform serial paste on the NFILES files named in FNAMPTR.
325 Return true if no errors, false if one or more files could not be
326 opened or read. */
327
328static bool
329paste_serial (size_t nfiles, char **fnamptr)
330{
331 bool ok = true; /* false if open or read errors occur. */
332 int charnew, charold; /* Current and previous char read. */
333 char const *delimptr; /* Current delimiter char. */
334 FILE *fileptr; /* Open for reading current file. */
335
336 for (; nfiles; nfiles--, fnamptr++)
337 {
338 int saved_errno;
339 bool is_stdin = STREQ (*fnamptr, "-");
340 if (is_stdin)
341 {
342 have_read_stdin = true;
343 fileptr = stdin;
344 }
345 else
346 {
347 fileptr = fopen (*fnamptr, "r");
348 if (fileptr == NULL)
349 {
350 error (0, errno, "%s", *fnamptr);
351 ok = false;
352 continue;
353 }
354 }
355
356 delimptr = delims; /* Set up for delimiter string. */
357
358 charold = getc (fileptr);
359 saved_errno = errno;
360 if (charold != EOF)
361 {
362 /* `charold' is set up. Hit it!
363 Keep reading characters, stashing them in `charnew';
364 output `charold', converting to the appropriate delimiter
365 character if needed. After the EOF, output `charold'
366 if it's a newline; otherwise, output it and then a newline. */
367
368 while ((charnew = getc (fileptr)) != EOF)
369 {
370 /* Process the old character. */
371 if (charold == '\n')
372 {
373 if (*delimptr != EMPTY_DELIM)
374 xputchar (*delimptr);
375
376 if (++delimptr == delim_end)
377 delimptr = delims;
378 }
379 else
380 xputchar (charold);
381
382 charold = charnew;
383 }
384 saved_errno = errno;
385
386 /* Hit EOF. Process that last character. */
387 xputchar (charold);
388 }
389
390 if (charold != '\n')
391 xputchar ('\n');
392
393 if (ferror (fileptr))
394 {
395 error (0, saved_errno, "%s", *fnamptr);
396 ok = false;
397 }
398 if (is_stdin)
399 clearerr (fileptr); /* Also clear EOF. */
400 else if (fclose (fileptr) == EOF)
401 {
402 error (0, errno, "%s", *fnamptr);
403 ok = false;
404 }
405 }
406 return ok;
407}
408
409void
410usage (int status)
411{
412 if (status != EXIT_SUCCESS)
413 fprintf (stderr, _("Try `%s --help' for more information.\n"),
414 program_name);
415 else
416 {
417 printf (_("\
418Usage: %s [OPTION]... [FILE]...\n\
419"),
420 program_name);
421 fputs (_("\
422Write lines consisting of the sequentially corresponding lines from\n\
423each FILE, separated by TABs, to standard output.\n\
424With no FILE, or when FILE is -, read standard input.\n\
425\n\
426"), stdout);
427 fputs (_("\
428Mandatory arguments to long options are mandatory for short options too.\n\
429"), stdout);
430 fputs (_("\
431 -d, --delimiters=LIST reuse characters from LIST instead of TABs\n\
432 -s, --serial paste one file at a time instead of in parallel\n\
433"), stdout);
434 fputs (HELP_OPTION_DESCRIPTION, stdout);
435 fputs (VERSION_OPTION_DESCRIPTION, stdout);
436 /* FIXME: add a couple of examples. */
437 printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
438 }
439 exit (status);
440}
441
442int
443main (int argc, char **argv)
444{
445 int optc;
446 bool ok;
447 char const *delim_arg = "\t";
448
449 initialize_main (&argc, &argv);
450 program_name = argv[0];
451 setlocale (LC_ALL, "");
452 bindtextdomain (PACKAGE, LOCALEDIR);
453 textdomain (PACKAGE);
454
455 atexit (close_stdout);
456
457 have_read_stdin = false;
458 serial_merge = false;
459
460 while ((optc = getopt_long (argc, argv, "d:s", longopts, NULL)) != -1)
461 {
462 switch (optc)
463 {
464 case 'd':
465 /* Delimiter character(s). */
466 if (optarg[0] == '\0')
467 optarg = "\\0";
468 delim_arg = optarg;
469 break;
470
471 case 's':
472 serial_merge = true;
473 break;
474
475 case_GETOPT_HELP_CHAR;
476
477 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
478
479 default:
480 usage (EXIT_FAILURE);
481 }
482 }
483
484 if (optind == argc)
485 argv[argc++] = "-";
486
487 collapse_escapes (delim_arg);
488
489 if (!serial_merge)
490 ok = paste_parallel (argc - optind, &argv[optind]);
491 else
492 ok = paste_serial (argc - optind, &argv[optind]);
493
494 free (delims);
495
496 if (have_read_stdin && fclose (stdin) == EOF)
497 error (EXIT_FAILURE, errno, "-");
498 exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
499}
Note: See TracBrowser for help on using the repository browser.