/* head -- output first part of file(s) Copyright (C) 89, 90, 91, 95, 96, 1997, 1998 Free Software Foundation, Inc. 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. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Options: (see usage) Reads from standard input if no files are given or when a filename of ``-'' is encountered. By default, filename headers are printed only if more than one file is given. By default, prints the first 10 lines (head -n 10). David MacKenzie */ #include #include #include #include #include "system.h" #include "error.h" #include "xstrtoul.h" #include "safe-read.h" /* FIXME: someday, make this really *be* `long long'. */ typedef long int U_LONG_LONG; /* Number of lines/chars/blocks to head. */ #define DEFAULT_NUMBER 10 /* Size of atomic reads. */ #define BUFSIZE (512 * 8) /* If nonzero, print filename headers. */ static int print_headers; /* When to print the filename banners. */ enum header_mode { multiple_files, always, never }; /* The name this program was run with. */ char *program_name; /* Have we ever read standard input? */ static int have_read_stdin; /* If nonzero, display usage information and exit. */ static int show_help; /* If nonzero, print the version on standard output then exit. */ static int show_version; static struct option const long_options[] = { {"bytes", required_argument, NULL, 'c'}, {"lines", required_argument, NULL, 'n'}, {"quiet", no_argument, NULL, 'q'}, {"silent", no_argument, NULL, 'q'}, {"verbose", no_argument, NULL, 'v'}, {"help", no_argument, &show_help, 1}, {"version", no_argument, &show_version, 1}, {NULL, 0, NULL, 0} }; static void usage (int status) { if (status != 0) fprintf (stderr, _("Try `%s --help' for more information.\n"), program_name); else { printf (_("\ Usage: %s [OPTION]... [FILE]...\n\ "), program_name); printf (_("\ Print first 10 lines of each FILE to standard output.\n\ With more than one FILE, precede each with a header giving the file name.\n\ With no FILE, or when FILE is -, read standard input.\n\ \n\ -c, --bytes=SIZE print first SIZE bytes\n\ -n, --lines=NUMBER print first NUMBER lines instead of first 10\n\ -q, --quiet, --silent never print headers giving file names\n\ -v, --verbose always print headers giving file names\n\ --help display this help and exit\n\ --version output version information and exit\n\ \n\ SIZE may have a multiplier suffix: b for 512, k for 1K, m for 1 Meg.\n\ If -VALUE is used as first OPTION, read -c VALUE when one of\n\ multipliers bkm follows concatenated, else read -n VALUE.\n\ ")); puts (_("\nReport bugs to .")); } exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE); } static void write_header (const char *filename) { static int first_file = 1; printf ("%s==> %s <==\n", (first_file ? "" : "\n"), filename); first_file = 0; } static int head_bytes (const char *filename, int fd, U_LONG_LONG bytes_to_write) { char buffer[BUFSIZE]; int bytes_read; while (bytes_to_write) { bytes_read = safe_read (fd, buffer, BUFSIZE); if (bytes_read < 0) { error (0, errno, "%s", filename); return 1; } if (bytes_read == 0) break; if (bytes_read > bytes_to_write) bytes_read = bytes_to_write; if (fwrite (buffer, 1, bytes_read, stdout) == 0) error (EXIT_FAILURE, errno, _("write error")); bytes_to_write -= bytes_read; } return 0; } static int head_lines (const char *filename, int fd, U_LONG_LONG lines_to_write) { char buffer[BUFSIZE]; int bytes_read; int bytes_to_write; while (lines_to_write) { bytes_read = safe_read (fd, buffer, BUFSIZE); if (bytes_read < 0) { error (0, errno, "%s", filename); return 1; } if (bytes_read == 0) break; bytes_to_write = 0; while (bytes_to_write < bytes_read) if (buffer[bytes_to_write++] == '\n' && --lines_to_write == 0) break; if (fwrite (buffer, 1, bytes_to_write, stdout) == 0) error (EXIT_FAILURE, errno, _("write error")); } return 0; } static int head (const char *filename, int fd, U_LONG_LONG n_units, int count_lines) { if (count_lines) return head_lines (filename, fd, n_units); else return head_bytes (filename, fd, n_units); } static int head_file (const char *filename, U_LONG_LONG n_units, int count_lines) { int fd; if (STREQ (filename, "-")) { have_read_stdin = 1; filename = _("standard input"); if (print_headers) write_header (filename); return head (filename, 0, n_units, count_lines); } else { fd = open (filename, O_RDONLY); if (fd >= 0) { int errors; if (print_headers) write_header (filename); errors = head (filename, fd, n_units, count_lines); if (close (fd) == 0) return errors; } error (0, errno, "%s", filename); return 1; } } /* Convert a string of digits, N_STRING, with a single, optional suffix character (b, k, or m) to an integral value. Upon successful conversion, return that value. If it cannot be converted, give a diagnostic and exit. COUNT_LINES indicates whether N_STRING is a number of bytes or a number of lines. It is used solely to give a more specific diagnostic. */ static U_LONG_LONG string_to_ull (int count_lines, const char *n_string) { strtol_error s_err; unsigned long int tmp_ulong; s_err = xstrtoul (n_string, NULL, 0, &tmp_ulong, "bkm"); if (s_err == LONGINT_INVALID) { error (EXIT_FAILURE, 0, "%s: %s", n_string, (count_lines ? _("invalid number of lines") : _("invalid number of bytes"))); } if (s_err != LONGINT_OK) { error (EXIT_FAILURE, 0, _("%s: %s is so large that it is not representable"), n_string, count_lines ? _("number of lines") : _("number of bytes")); } return tmp_ulong; } int main (int argc, char **argv) { enum header_mode header_mode = multiple_files; int exit_status = 0; char *n_string; int c; /* Number of items to print. */ U_LONG_LONG n_units = DEFAULT_NUMBER; /* If nonzero, interpret the numeric argument as the number of lines. Otherwise, interpret it as the number of bytes. */ int count_lines = 1; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); have_read_stdin = 0; print_headers = 0; if (argc > 1 && argv[1][0] == '-' && ISDIGIT (argv[1][1])) { char *end_n_string; char multiplier_char = 0; n_string = &argv[1][1]; /* Old option syntax; a dash, one or more digits, and one or more option letters. Move past the number. */ for (++argv[1]; ISDIGIT (*argv[1]); ++argv[1]) { /* empty */ } /* Pointer to the byte after the last digit. */ end_n_string = argv[1]; /* Parse any appended option letters. */ while (*argv[1]) { switch (*argv[1]) { case 'c': count_lines = 0; multiplier_char = 0; break; case 'b': case 'k': case 'm': count_lines = 0; multiplier_char = *argv[1]; break; case 'l': count_lines = 1; break; case 'q': header_mode = never; break; case 'v': header_mode = always; break; default: error (0, 0, _("unrecognized option `-%c'"), *argv[1]); usage (1); } ++argv[1]; } /* Append the multiplier character (if any) onto the end of the digit string. Then add NUL byte if necessary. */ *end_n_string = multiplier_char; if (multiplier_char) *(++end_n_string) = 0; n_units = string_to_ull (count_lines, n_string); /* Make the options we just parsed invisible to getopt. */ argv[1] = argv[0]; argv++; argc--; /* FIXME: allow POSIX options if there were obsolescent ones? */ } while ((c = getopt_long (argc, argv, "c:n:qv", long_options, NULL)) != -1) { switch (c) { case 0: break; case 'c': count_lines = 0; n_units = string_to_ull (count_lines, optarg); break; case 'n': count_lines = 1; n_units = string_to_ull (count_lines, optarg); break; case 'q': header_mode = never; break; case 'v': header_mode = always; break; default: usage (1); } } if (show_version) { printf ("head (%s) %s\n", GNU_PACKAGE, VERSION); exit (EXIT_SUCCESS); } if (show_help) usage (0); if (header_mode == always || (header_mode == multiple_files && optind < argc - 1)) print_headers = 1; if (optind == argc) exit_status |= head_file ("-", n_units, count_lines); for (; optind < argc; ++optind) exit_status |= head_file (argv[optind], n_units, count_lines); if (have_read_stdin && close (0) < 0) error (EXIT_FAILURE, errno, "-"); if (fclose (stdout) == EOF) error (EXIT_FAILURE, errno, _("write error")); exit (exit_status == 0 ? EXIT_SUCCESS : EXIT_FAILURE); }