1
0
mirror of git://git.sv.gnu.org/coreutils.git synced 2026-03-20 03:44:18 +02:00
Files
coreutils/src/nl.c
Pádraig Brady 5d4f09d83a doc: mention the texinfo documentation in --help
* src/system.h: Rename emit_bug_reporting_address() to
emit_ancillary_info() and update it to not print the translation
project address in en_* locales, and _do_ print it in the 'C'
(and other) locales so that it's included in the default man page.
Also mention how to invoke the texinfo documentation for each command.
Also move the "hard-locale.h" include to the 8 files that now use it.
* man/help2man: Strip the newly added texinfo reference from the
--help output as a more verbose version is already added by help2man.
Suggestion from C de-Avillez
2009-09-21 12:37:57 +01:00

627 lines
16 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* nl -- number lines of files
Copyright (C) 89, 92, 1995-2009 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 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
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, see <http://www.gnu.org/licenses/>. */
/* Written by Scott Bartram (nancy!scott@uunet.uu.net)
Revised by David MacKenzie (djm@gnu.ai.mit.edu) */
#include <config.h>
#include <stdio.h>
#include <sys/types.h>
#include <getopt.h>
#include "system.h"
#include <regex.h>
#include "error.h"
#include "linebuffer.h"
#include "quote.h"
#include "xstrtol.h"
/* The official name of this program (e.g., no `g' prefix). */
#define PROGRAM_NAME "nl"
#define AUTHORS \
proper_name ("Scott Bartram"), \
proper_name ("David MacKenzie")
/* Line-number formats. They are given an int width, an intmax_t
value, and a string separator. */
/* Right justified, no leading zeroes. */
static char const FORMAT_RIGHT_NOLZ[] = "%*" PRIdMAX "%s";
/* Right justified, leading zeroes. */
static char const FORMAT_RIGHT_LZ[] = "%0*" PRIdMAX "%s";
/* Left justified, no leading zeroes. */
static char const FORMAT_LEFT[] = "%-*" PRIdMAX "%s";
/* Default section delimiter characters. */
static char const DEFAULT_SECTION_DELIMITERS[] = "\\:";
/* Types of input lines: either one of the section delimiters,
or text to output. */
enum section
{
Header, Body, Footer, Text
};
/* Format of body lines (-b). */
static char const *body_type = "t";
/* Format of header lines (-h). */
static char const *header_type = "n";
/* Format of footer lines (-f). */
static char const *footer_type = "n";
/* Format currently being used (body, header, or footer). */
static char const *current_type;
/* Regex for body lines to number (-bp). */
static struct re_pattern_buffer body_regex;
/* Regex for header lines to number (-hp). */
static struct re_pattern_buffer header_regex;
/* Regex for footer lines to number (-fp). */
static struct re_pattern_buffer footer_regex;
/* Fastmaps for the above. */
static char body_fastmap[UCHAR_MAX + 1];
static char header_fastmap[UCHAR_MAX + 1];
static char footer_fastmap[UCHAR_MAX + 1];
/* Pointer to current regex, if any. */
static struct re_pattern_buffer *current_regex = NULL;
/* Separator string to print after line number (-s). */
static char const *separator_str = "\t";
/* Input section delimiter string (-d). */
static char const *section_del = DEFAULT_SECTION_DELIMITERS;
/* Header delimiter string. */
static char *header_del = NULL;
/* Header section delimiter length. */
static size_t header_del_len;
/* Body delimiter string. */
static char *body_del = NULL;
/* Body section delimiter length. */
static size_t body_del_len;
/* Footer delimiter string. */
static char *footer_del = NULL;
/* Footer section delimiter length. */
static size_t footer_del_len;
/* Input buffer. */
static struct linebuffer line_buf;
/* printf format string for unnumbered lines. */
static char *print_no_line_fmt = NULL;
/* Starting line number on each page (-v). */
static intmax_t starting_line_number = 1;
/* Line number increment (-i). */
static intmax_t page_incr = 1;
/* If true, reset line number at start of each page (-p). */
static bool reset_numbers = true;
/* Number of blank lines to consider to be one line for numbering (-l). */
static intmax_t blank_join = 1;
/* Width of line numbers (-w). */
static int lineno_width = 6;
/* Line number format (-n). */
static char const *lineno_format = FORMAT_RIGHT_NOLZ;
/* Current print line number. */
static intmax_t line_no;
/* True if we have ever read standard input. */
static bool have_read_stdin;
enum
{
PAGE_INCREMENT_OPTION_DEPRECATED = CHAR_MAX + 1
};
static struct option const longopts[] =
{
{"header-numbering", required_argument, NULL, 'h'},
{"body-numbering", required_argument, NULL, 'b'},
{"footer-numbering", required_argument, NULL, 'f'},
{"starting-line-number", required_argument, NULL, 'v'},
{"line-increment", required_argument, NULL, 'i'},
/* FIXME: page-increment is deprecated, remove in dec-2011. */
{"page-increment", required_argument, NULL, PAGE_INCREMENT_OPTION_DEPRECATED},
{"no-renumber", no_argument, NULL, 'p'},
{"join-blank-lines", required_argument, NULL, 'l'},
{"number-separator", required_argument, NULL, 's'},
{"number-width", required_argument, NULL, 'w'},
{"number-format", required_argument, NULL, 'n'},
{"section-delimiter", required_argument, NULL, 'd'},
{GETOPT_HELP_OPTION_DECL},
{GETOPT_VERSION_OPTION_DECL},
{NULL, 0, NULL, 0}
};
/* Print a usage message and quit. */
void
usage (int status)
{
if (status != EXIT_SUCCESS)
fprintf (stderr, _("Try `%s --help' for more information.\n"),
program_name);
else
{
printf (_("\
Usage: %s [OPTION]... [FILE]...\n\
"),
program_name);
fputs (_("\
Write each FILE to standard output, with line numbers added.\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);
fputs (_("\
-b, --body-numbering=STYLE use STYLE for numbering body lines\n\
-d, --section-delimiter=CC use CC for separating logical pages\n\
-f, --footer-numbering=STYLE use STYLE for numbering footer lines\n\
"), stdout);
fputs (_("\
-h, --header-numbering=STYLE use STYLE for numbering header lines\n\
-i, --line-increment=NUMBER line number increment at each line\n\
-l, --join-blank-lines=NUMBER group of NUMBER empty lines counted as one\n\
-n, --number-format=FORMAT insert line numbers according to FORMAT\n\
-p, --no-renumber do not reset line numbers at logical pages\n\
-s, --number-separator=STRING add STRING after (possible) line number\n\
"), stdout);
fputs (_("\
-v, --starting-line-number=NUMBER first line number on each logical page\n\
-w, --number-width=NUMBER use NUMBER columns for line numbers\n\
"), stdout);
fputs (HELP_OPTION_DESCRIPTION, stdout);
fputs (VERSION_OPTION_DESCRIPTION, stdout);
fputs (_("\
\n\
By default, selects -v1 -i1 -l1 -sTAB -w6 -nrn -hn -bt -fn. CC are\n\
two delimiter characters for separating logical pages, a missing\n\
second character implies :. Type \\\\ for \\. STYLE is one of:\n\
"), stdout);
fputs (_("\
\n\
a number all lines\n\
t number only nonempty lines\n\
n number no lines\n\
pBRE number only lines that contain a match for the basic regular\n\
expression, BRE\n\
\n\
FORMAT is one of:\n\
\n\
ln left justified, no leading zeros\n\
rn right justified, no leading zeros\n\
rz right justified, leading zeros\n\
\n\
"), stdout);
emit_ancillary_info ();
}
exit (status);
}
/* Set the command line flag TYPEP and possibly the regex pointer REGEXP,
according to `optarg'. */
static bool
build_type_arg (char const **typep,
struct re_pattern_buffer *regexp, char *fastmap)
{
char const *errmsg;
bool rval = true;
switch (*optarg)
{
case 'a':
case 't':
case 'n':
*typep = optarg;
break;
case 'p':
*typep = optarg++;
regexp->buffer = NULL;
regexp->allocated = 0;
regexp->fastmap = fastmap;
regexp->translate = NULL;
re_syntax_options =
RE_SYNTAX_POSIX_BASIC & ~RE_CONTEXT_INVALID_DUP & ~RE_NO_EMPTY_RANGES;
errmsg = re_compile_pattern (optarg, strlen (optarg), regexp);
if (errmsg)
error (EXIT_FAILURE, 0, "%s", errmsg);
break;
default:
rval = false;
break;
}
return rval;
}
/* Print the line number and separator; increment the line number. */
static void
print_lineno (void)
{
intmax_t next_line_no;
printf (lineno_format, lineno_width, line_no, separator_str);
next_line_no = line_no + page_incr;
if (next_line_no < line_no)
error (EXIT_FAILURE, 0, _("line number overflow"));
line_no = next_line_no;
}
/* Switch to a header section. */
static void
proc_header (void)
{
current_type = header_type;
current_regex = &header_regex;
if (reset_numbers)
line_no = starting_line_number;
putchar ('\n');
}
/* Switch to a body section. */
static void
proc_body (void)
{
current_type = body_type;
current_regex = &body_regex;
putchar ('\n');
}
/* Switch to a footer section. */
static void
proc_footer (void)
{
current_type = footer_type;
current_regex = &footer_regex;
putchar ('\n');
}
/* Process a regular text line in `line_buf'. */
static void
proc_text (void)
{
static intmax_t blank_lines = 0; /* Consecutive blank lines so far. */
switch (*current_type)
{
case 'a':
if (blank_join > 1)
{
if (1 < line_buf.length || ++blank_lines == blank_join)
{
print_lineno ();
blank_lines = 0;
}
else
fputs (print_no_line_fmt, stdout);
}
else
print_lineno ();
break;
case 't':
if (1 < line_buf.length)
print_lineno ();
else
fputs (print_no_line_fmt, stdout);
break;
case 'n':
fputs (print_no_line_fmt, stdout);
break;
case 'p':
switch (re_search (current_regex, line_buf.buffer, line_buf.length - 1,
0, line_buf.length - 1, NULL))
{
case -2:
error (EXIT_FAILURE, errno, _("error in regular expression search"));
case -1:
fputs (print_no_line_fmt, stdout);
break;
default:
print_lineno ();
break;
}
}
fwrite (line_buf.buffer, sizeof (char), line_buf.length, stdout);
}
/* Return the type of line in `line_buf'. */
static enum section
check_section (void)
{
size_t len = line_buf.length - 1;
if (len < 2 || memcmp (line_buf.buffer, section_del, 2))
return Text;
if (len == header_del_len
&& !memcmp (line_buf.buffer, header_del, header_del_len))
return Header;
if (len == body_del_len
&& !memcmp (line_buf.buffer, body_del, body_del_len))
return Body;
if (len == footer_del_len
&& !memcmp (line_buf.buffer, footer_del, footer_del_len))
return Footer;
return Text;
}
/* Read and process the file pointed to by FP. */
static void
process_file (FILE *fp)
{
while (readlinebuffer (&line_buf, fp))
{
switch (check_section ())
{
case Header:
proc_header ();
break;
case Body:
proc_body ();
break;
case Footer:
proc_footer ();
break;
case Text:
proc_text ();
break;
}
}
}
/* Process file FILE to standard output.
Return true if successful. */
static bool
nl_file (char const *file)
{
FILE *stream;
if (STREQ (file, "-"))
{
have_read_stdin = true;
stream = stdin;
}
else
{
stream = fopen (file, "r");
if (stream == NULL)
{
error (0, errno, "%s", file);
return false;
}
}
process_file (stream);
if (ferror (stream))
{
error (0, errno, "%s", file);
return false;
}
if (STREQ (file, "-"))
clearerr (stream); /* Also clear EOF. */
else if (fclose (stream) == EOF)
{
error (0, errno, "%s", file);
return false;
}
return true;
}
int
main (int argc, char **argv)
{
int c;
size_t len;
bool ok = true;
initialize_main (&argc, &argv);
set_program_name (argv[0]);
setlocale (LC_ALL, "");
bindtextdomain (PACKAGE, LOCALEDIR);
textdomain (PACKAGE);
atexit (close_stdout);
have_read_stdin = false;
while ((c = getopt_long (argc, argv, "h:b:f:v:i:pl:s:w:n:d:", longopts,
NULL)) != -1)
{
switch (c)
{
case 'h':
if (! build_type_arg (&header_type, &header_regex, header_fastmap))
{
error (0, 0, _("invalid header numbering style: %s"),
quote (optarg));
ok = false;
}
break;
case 'b':
if (! build_type_arg (&body_type, &body_regex, body_fastmap))
{
error (0, 0, _("invalid body numbering style: %s"),
quote (optarg));
ok = false;
}
break;
case 'f':
if (! build_type_arg (&footer_type, &footer_regex, footer_fastmap))
{
error (0, 0, _("invalid footer numbering style: %s"),
quote (optarg));
ok = false;
}
break;
case 'v':
if (xstrtoimax (optarg, NULL, 10, &starting_line_number, "")
!= LONGINT_OK)
{
error (0, 0, _("invalid starting line number: %s"),
quote (optarg));
ok = false;
}
break;
case PAGE_INCREMENT_OPTION_DEPRECATED:
error (0, 0, _("WARNING: --page-increment is deprecated; "
"use --line-increment instead"));
/* fall through */
case 'i':
if (! (xstrtoimax (optarg, NULL, 10, &page_incr, "") == LONGINT_OK
&& 0 < page_incr))
{
error (0, 0, _("invalid line number increment: %s"),
quote (optarg));
ok = false;
}
break;
case 'p':
reset_numbers = false;
break;
case 'l':
if (! (xstrtoimax (optarg, NULL, 10, &blank_join, "") == LONGINT_OK
&& 0 < blank_join))
{
error (0, 0, _("invalid number of blank lines: %s"),
quote (optarg));
ok = false;
}
break;
case 's':
separator_str = optarg;
break;
case 'w':
{
long int tmp_long;
if (xstrtol (optarg, NULL, 10, &tmp_long, "") != LONGINT_OK
|| tmp_long <= 0 || tmp_long > INT_MAX)
{
error (0, 0, _("invalid line number field width: %s"),
quote (optarg));
ok = false;
}
else
{
lineno_width = tmp_long;
}
}
break;
case 'n':
if (STREQ (optarg, "ln"))
lineno_format = FORMAT_LEFT;
else if (STREQ (optarg, "rn"))
lineno_format = FORMAT_RIGHT_NOLZ;
else if (STREQ (optarg, "rz"))
lineno_format = FORMAT_RIGHT_LZ;
else
{
error (0, 0, _("invalid line numbering format: %s"),
quote (optarg));
ok = false;
}
break;
case 'd':
section_del = optarg;
break;
case_GETOPT_HELP_CHAR;
case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
default:
ok = false;
break;
}
}
if (!ok)
usage (EXIT_FAILURE);
/* Initialize the section delimiters. */
len = strlen (section_del);
header_del_len = len * 3;
header_del = xmalloc (header_del_len + 1);
stpcpy (stpcpy (stpcpy (header_del, section_del), section_del), section_del);
body_del_len = len * 2;
body_del = xmalloc (body_del_len + 1);
stpcpy (stpcpy (body_del, section_del), section_del);
footer_del_len = len;
footer_del = xmalloc (footer_del_len + 1);
stpcpy (footer_del, section_del);
/* Initialize the input buffer. */
initbuffer (&line_buf);
/* Initialize the printf format for unnumbered lines. */
len = strlen (separator_str);
print_no_line_fmt = xmalloc (lineno_width + len + 1);
memset (print_no_line_fmt, ' ', lineno_width + len);
print_no_line_fmt[lineno_width + len] = '\0';
line_no = starting_line_number;
current_type = body_type;
current_regex = &body_regex;
/* Main processing. */
if (optind == argc)
ok = nl_file ("-");
else
for (; optind < argc; optind++)
ok &= nl_file (argv[optind]);
if (have_read_stdin && fclose (stdin) == EOF)
error (EXIT_FAILURE, errno, "-");
exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
}