diff options
Diffstat (limited to 'src/paste.c')
-rw-r--r-- | src/paste.c | 535 |
1 files changed, 284 insertions, 251 deletions
diff --git a/src/paste.c b/src/paste.c index 414fb88..bf99fe0 100644 --- a/src/paste.c +++ b/src/paste.c @@ -1,11 +1,11 @@ /* paste - merge lines of files - Copyright (C) 1997-2005 Free Software Foundation, Inc. + Copyright (C) 1997-2016 Free Software Foundation, Inc. Copyright (C) 1984 David M. Ihnat - This program is free software; you can redistribute it and/or modify + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2, or (at your option) - any later version. + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -13,8 +13,7 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software Foundation, - Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + along with this program. If not, see <http://www.gnu.org/licenses/>. */ /* Written by David Ihnat. */ @@ -27,13 +26,13 @@ Options: --serial -s Paste one file at a time rather than - one line from each file. + one line from each file. --delimiters=delim-list -d delim-list Consecutively use the characters in - DELIM-LIST instead of tab to separate - merged lines. When DELIM-LIST is exhausted, - start again at its beginning. - A FILE of `-' means standard input. + DELIM-LIST instead of tab to separate + merged lines. When DELIM-LIST is exhausted, + start again at its beginning. + A FILE of '-' means standard input. If no FILEs are given, standard input is used. */ #include <config.h> @@ -43,18 +42,18 @@ #include <sys/types.h> #include "system.h" #include "error.h" +#include "fadvise.h" -/* The official name of this program (e.g., no `g' prefix). */ +/* The official name of this program (e.g., no 'g' prefix). */ #define PROGRAM_NAME "paste" -#define AUTHORS "David M. Ihnat", "David MacKenzie" +#define AUTHORS \ + proper_name ("David M. Ihnat"), \ + proper_name ("David MacKenzie") /* Indicates that no delimiter should be added in the current position. */ #define EMPTY_DELIM '\0' -/* Name this program was run with. */ -char *program_name; - /* If nonzero, we have read standard input at some point. */ static bool have_read_stdin; @@ -62,16 +61,19 @@ static bool have_read_stdin; corresponding lines from each file in parallel. */ static bool serial_merge; -/* The delimeters between lines of input files (used cyclically). */ +/* The delimiters between lines of input files (used cyclically). */ static char *delims; -/* A pointer to the character after the end of `delims'. */ +/* A pointer to the character after the end of 'delims'. */ static char const *delim_end; +static unsigned char line_delim = '\n'; + static struct option const longopts[] = { {"serial", no_argument, NULL, 's'}, {"delimiters", required_argument, NULL, 'd'}, + {"zero-terminated", no_argument, NULL, 'z'}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} @@ -80,58 +82,75 @@ static struct option const longopts[] = /* Set globals delims and delim_end. Copy STRPTR to DELIMS, converting backslash representations of special characters in STRPTR to their actual values. The set of possible backslash characters has been expanded beyond - that recognized by the Unix version. */ + that recognized by the Unix version. + Return 0 upon success. + If the string ends in an odd number of backslashes, ignore the + final backslash and return nonzero. */ -static void +static int collapse_escapes (char const *strptr) { char *strout = xstrdup (strptr); + bool backslash_at_end = false; + delims = strout; while (*strptr) { if (*strptr != '\\') /* Is it an escape character? */ - *strout++ = *strptr++; /* No, just transfer it. */ + *strout++ = *strptr++; /* No, just transfer it. */ else - { - switch (*++strptr) - { - case '0': - *strout++ = EMPTY_DELIM; - break; - - case 'b': - *strout++ = '\b'; - break; - - case 'f': - *strout++ = '\f'; - break; - - case 'n': - *strout++ = '\n'; - break; - - case 'r': - *strout++ = '\r'; - break; - - case 't': - *strout++ = '\t'; - break; - - case 'v': - *strout++ = '\v'; - break; - - default: - *strout++ = *strptr; - break; - } - strptr++; - } + { + switch (*++strptr) + { + case '0': + *strout++ = EMPTY_DELIM; + break; + + case 'b': + *strout++ = '\b'; + break; + + case 'f': + *strout++ = '\f'; + break; + + case 'n': + *strout++ = '\n'; + break; + + case 'r': + *strout++ = '\r'; + break; + + case 't': + *strout++ = '\t'; + break; + + case 'v': + *strout++ = '\v'; + break; + + case '\\': + *strout++ = '\\'; + break; + + case '\0': + backslash_at_end = true; + goto done; + + default: + *strout++ = *strptr; + break; + } + strptr++; + } } + + done: + delim_end = strout; + return backslash_at_end ? 1 : 0; } /* Report a write error and exit. */ @@ -184,18 +203,19 @@ paste_parallel (size_t nfiles, char **fnamptr) for (files_open = 0; files_open < nfiles; ++files_open) { if (STREQ (fnamptr[files_open], "-")) - { - have_read_stdin = true; - fileptr[files_open] = stdin; - } + { + have_read_stdin = true; + fileptr[files_open] = stdin; + } else - { - fileptr[files_open] = fopen (fnamptr[files_open], "r"); - if (fileptr[files_open] == NULL) - error (EXIT_FAILURE, errno, "%s", fnamptr[files_open]); - else if (fileno (fileptr[files_open]) == STDIN_FILENO) - opened_stdin = true; - } + { + fileptr[files_open] = fopen (fnamptr[files_open], "r"); + if (fileptr[files_open] == NULL) + error (EXIT_FAILURE, errno, "%s", quotef (fnamptr[files_open])); + else if (fileno (fileptr[files_open]) == STDIN_FILENO) + opened_stdin = true; + fadvise (fileptr[files_open], FADVISE_SEQUENTIAL); + } } if (opened_stdin && have_read_stdin) @@ -210,111 +230,111 @@ paste_parallel (size_t nfiles, char **fnamptr) /* Set up for the next line. */ bool somedone = false; char const *delimptr = delims; - size_t delims_saved = 0; /* Number of delims saved in `delbuf'. */ + size_t delims_saved = 0; /* Number of delims saved in 'delbuf'. */ size_t i; for (i = 0; i < nfiles && files_open; i++) - { - int chr IF_LINT (= 0); /* Input character. */ - int err IF_LINT (= 0); /* Input errno value. */ - size_t line_length = 0; /* Number of chars in line. */ - - if (fileptr[i]) - { - chr = getc (fileptr[i]); - err = errno; - if (chr != EOF && delims_saved) - { - if (fwrite (delbuf, 1, delims_saved, stdout) != delims_saved) - write_error (); - delims_saved = 0; - } - - while (chr != EOF) - { - line_length++; - if (chr == '\n') - break; - xputchar (chr); - chr = getc (fileptr[i]); - err = errno; - } - } - - if (line_length == 0) - { - /* EOF, read error, or closed file. - If an EOF or error, close the file. */ - if (fileptr[i]) - { - if (ferror (fileptr[i])) - { - error (0, err, "%s", fnamptr[i]); - ok = false; - } - if (fileptr[i] == stdin) - clearerr (fileptr[i]); /* Also clear EOF. */ - else if (fclose (fileptr[i]) == EOF) - { - error (0, errno, "%s", fnamptr[i]); - ok = false; - } - - fileptr[i] = NULL; - files_open--; - } - - if (i + 1 == nfiles) - { - /* End of this output line. - Is this the end of the whole thing? */ - if (somedone) - { - /* No. Some files were not closed for this line. */ - if (delims_saved) - { - if (fwrite (delbuf, 1, delims_saved, stdout) - != delims_saved) - write_error (); - delims_saved = 0; - } - xputchar ('\n'); - } - continue; /* Next read of files, or exit. */ - } - else - { - /* Closed file; add delimiter to `delbuf'. */ - if (*delimptr != EMPTY_DELIM) - delbuf[delims_saved++] = *delimptr; - if (++delimptr == delim_end) - delimptr = delims; - } - } - else - { - /* Some data read. */ - somedone = true; - - /* Except for last file, replace last newline with delim. */ - if (i + 1 != nfiles) - { - if (chr != '\n' && chr != EOF) - xputchar (chr); - if (*delimptr != EMPTY_DELIM) - xputchar (*delimptr); - if (++delimptr == delim_end) - delimptr = delims; - } - else - { - /* If the last line of the last file lacks a newline, - print one anyhow. POSIX requires this. */ - char c = (chr == EOF ? '\n' : chr); - xputchar (c); - } - } - } + { + int chr IF_LINT ( = 0); /* Input character. */ + int err IF_LINT ( = 0); /* Input errno value. */ + bool sometodo = false; /* Input chars to process. */ + + if (fileptr[i]) + { + chr = getc (fileptr[i]); + err = errno; + if (chr != EOF && delims_saved) + { + if (fwrite (delbuf, 1, delims_saved, stdout) != delims_saved) + write_error (); + delims_saved = 0; + } + + while (chr != EOF) + { + sometodo = true; + if (chr == line_delim) + break; + xputchar (chr); + chr = getc (fileptr[i]); + err = errno; + } + } + + if (! sometodo) + { + /* EOF, read error, or closed file. + If an EOF or error, close the file. */ + if (fileptr[i]) + { + if (ferror (fileptr[i])) + { + error (0, err, "%s", quotef (fnamptr[i])); + ok = false; + } + if (fileptr[i] == stdin) + clearerr (fileptr[i]); /* Also clear EOF. */ + else if (fclose (fileptr[i]) == EOF) + { + error (0, errno, "%s", quotef (fnamptr[i])); + ok = false; + } + + fileptr[i] = NULL; + files_open--; + } + + if (i + 1 == nfiles) + { + /* End of this output line. + Is this the end of the whole thing? */ + if (somedone) + { + /* No. Some files were not closed for this line. */ + if (delims_saved) + { + if (fwrite (delbuf, 1, delims_saved, stdout) + != delims_saved) + write_error (); + delims_saved = 0; + } + xputchar (line_delim); + } + continue; /* Next read of files, or exit. */ + } + else + { + /* Closed file; add delimiter to 'delbuf'. */ + if (*delimptr != EMPTY_DELIM) + delbuf[delims_saved++] = *delimptr; + if (++delimptr == delim_end) + delimptr = delims; + } + } + else + { + /* Some data read. */ + somedone = true; + + /* Except for last file, replace last newline with delim. */ + if (i + 1 != nfiles) + { + if (chr != line_delim && chr != EOF) + xputchar (chr); + if (*delimptr != EMPTY_DELIM) + xputchar (*delimptr); + if (++delimptr == delim_end) + delimptr = delims; + } + else + { + /* If the last line of the last file lacks a newline, + print one anyhow. POSIX requires this. */ + char c = (chr == EOF ? line_delim : chr); + xputchar (c); + } + } + } } free (fileptr); free (delbuf); @@ -338,70 +358,71 @@ paste_serial (size_t nfiles, char **fnamptr) int saved_errno; bool is_stdin = STREQ (*fnamptr, "-"); if (is_stdin) - { - have_read_stdin = true; - fileptr = stdin; - } + { + have_read_stdin = true; + fileptr = stdin; + } else - { - fileptr = fopen (*fnamptr, "r"); - if (fileptr == NULL) - { - error (0, errno, "%s", *fnamptr); - ok = false; - continue; - } - } + { + fileptr = fopen (*fnamptr, "r"); + if (fileptr == NULL) + { + error (0, errno, "%s", quotef (*fnamptr)); + ok = false; + continue; + } + fadvise (fileptr, FADVISE_SEQUENTIAL); + } delimptr = delims; /* Set up for delimiter string. */ charold = getc (fileptr); saved_errno = errno; if (charold != EOF) - { - /* `charold' is set up. Hit it! - Keep reading characters, stashing them in `charnew'; - output `charold', converting to the appropriate delimiter - character if needed. After the EOF, output `charold' - if it's a newline; otherwise, output it and then a newline. */ - - while ((charnew = getc (fileptr)) != EOF) - { - /* Process the old character. */ - if (charold == '\n') - { - if (*delimptr != EMPTY_DELIM) - xputchar (*delimptr); - - if (++delimptr == delim_end) - delimptr = delims; - } - else - xputchar (charold); - - charold = charnew; - } - saved_errno = errno; - - /* Hit EOF. Process that last character. */ - xputchar (charold); - } - - if (charold != '\n') - xputchar ('\n'); + { + /* 'charold' is set up. Hit it! + Keep reading characters, stashing them in 'charnew'; + output 'charold', converting to the appropriate delimiter + character if needed. After the EOF, output 'charold' + if it's a newline; otherwise, output it and then a newline. */ + + while ((charnew = getc (fileptr)) != EOF) + { + /* Process the old character. */ + if (charold == line_delim) + { + if (*delimptr != EMPTY_DELIM) + xputchar (*delimptr); + + if (++delimptr == delim_end) + delimptr = delims; + } + else + xputchar (charold); + + charold = charnew; + } + saved_errno = errno; + + /* Hit EOF. Process that last character. */ + xputchar (charold); + } + + if (charold != line_delim) + xputchar (line_delim); if (ferror (fileptr)) - { - error (0, saved_errno, "%s", *fnamptr); - ok = false; - } + { + error (0, saved_errno, "%s", quotef (*fnamptr)); + ok = false; + } if (is_stdin) - clearerr (fileptr); /* Also clear EOF. */ + clearerr (fileptr); /* Also clear EOF. */ else if (fclose (fileptr) == EOF) - { - error (0, errno, "%s", *fnamptr); - ok = false; - } + { + error (0, errno, "%s", quotef (*fnamptr)); + ok = false; + } } return ok; } @@ -410,31 +431,32 @@ void usage (int status) { if (status != EXIT_SUCCESS) - fprintf (stderr, _("Try `%s --help' for more information.\n"), - program_name); + emit_try_help (); else { printf (_("\ Usage: %s [OPTION]... [FILE]...\n\ "), - program_name); + program_name); fputs (_("\ Write lines consisting of the sequentially corresponding lines from\n\ each FILE, separated by TABs, to standard output.\n\ -With no FILE, or when FILE is -, read standard input.\n\ -\n\ -"), stdout); - fputs (_("\ -Mandatory arguments to long options are mandatory for short options too.\n\ "), stdout); + + emit_stdin_note (); + emit_mandatory_arg_note (); + fputs (_("\ -d, --delimiters=LIST reuse characters from LIST instead of TABs\n\ -s, --serial paste one file at a time instead of in parallel\n\ "), stdout); + fputs (_("\ + -z, --zero-terminated line delimiter is NUL, not newline\n\ +"), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); /* FIXME: add a couple of examples. */ - printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + emit_ancillary_info (PROGRAM_NAME); } exit (status); } @@ -447,7 +469,7 @@ main (int argc, char **argv) char const *delim_arg = "\t"; initialize_main (&argc, &argv); - program_name = argv[0]; + set_program_name (argv[0]); setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); @@ -457,32 +479,43 @@ main (int argc, char **argv) have_read_stdin = false; serial_merge = false; - while ((optc = getopt_long (argc, argv, "d:s", longopts, NULL)) != -1) + while ((optc = getopt_long (argc, argv, "d:sz", longopts, NULL)) != -1) { switch (optc) - { - case 'd': - /* Delimiter character(s). */ - delim_arg = (optarg[0] == '\0' ? "\\0" : optarg); - break; + { + case 'd': + /* Delimiter character(s). */ + delim_arg = (optarg[0] == '\0' ? "\\0" : optarg); + break; - case 's': - serial_merge = true; - break; + case 's': + serial_merge = true; + break; - case_GETOPT_HELP_CHAR; + case 'z': + line_delim = '\0'; + break; - case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + case_GETOPT_HELP_CHAR; - default: - usage (EXIT_FAILURE); - } + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + usage (EXIT_FAILURE); + } } if (optind == argc) - argv[argc++] = "-"; + argv[argc++] = bad_cast ("-"); - collapse_escapes (delim_arg); + if (collapse_escapes (delim_arg)) + { + /* Don't use the quote() quoting style, because that would double the + number of displayed backslashes, making the diagnostic look bogus. */ + error (EXIT_FAILURE, 0, + _("delimiter list ends with an unescaped backslash: %s"), + quotearg_n_style_colon (0, c_maybe_quoting_style, delim_arg)); + } if (!serial_merge) ok = paste_parallel (argc - optind, &argv[optind]); @@ -493,5 +526,5 @@ main (int argc, char **argv) if (have_read_stdin && fclose (stdin) == EOF) error (EXIT_FAILURE, errno, "-"); - exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); + return ok ? EXIT_SUCCESS : EXIT_FAILURE; } |