diff --git a/usr.bin/Makefile b/usr.bin/Makefile index 58ce7e1918da..6879eae6a0f7 100644 --- a/usr.bin/Makefile +++ b/usr.bin/Makefile @@ -111,6 +111,7 @@ SUBDIR= apply \ newkey \ nfsstat \ nice \ + nl \ nohup \ objformat \ opieinfo \ diff --git a/usr.bin/nl/Makefile b/usr.bin/nl/Makefile new file mode 100644 index 000000000000..9b1f292dfbee --- /dev/null +++ b/usr.bin/nl/Makefile @@ -0,0 +1,5 @@ +# $FreeBSD$ + +PROG= nl + +.include diff --git a/usr.bin/nl/nl.1 b/usr.bin/nl/nl.1 new file mode 100644 index 000000000000..501e61b3ee91 --- /dev/null +++ b/usr.bin/nl/nl.1 @@ -0,0 +1,242 @@ +.\" $FreeBSD$ +.\" +.\" Copyright (c) 1999 The NetBSD Foundation, Inc. +.\" All rights reserved. +.\" +.\" This code is derived from software contributed to The NetBSD Foundation +.\" by Klaus Klein. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. All advertising materials mentioning features or use of this software +.\" must display the following acknowledgement: +.\" This product includes software developed by the NetBSD +.\" Foundation, Inc. and its contributors. +.\" 4. Neither the name of The NetBSD Foundation nor the names of its +.\" contributors may be used to endorse or promote products derived +.\" from this software without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS +.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS +.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +.\" POSSIBILITY OF SUCH DAMAGE. +.\" +.Dd February 15, 1999 +.Dt NL 1 +.Os +.Sh NAME +.Nm \&nl +.Nd line numbering filter +.Sh SYNOPSIS +.Nm "" +.Op Fl p +.Bk -words +.Op Fl b Ar type +.Ek +.Bk -words +.Op Fl d Ar delim +.Ek +.Bk -words +.Op Fl f Ar type +.Ek +.Bk -words +.Op Fl h Ar type +.Ek +.Bk -words +.Op Fl i Ar incr +.Ek +.Bk -words +.Op Fl l Ar num +.Ek +.Bk -words +.Op Fl n Ar format +.Ek +.Bk -words +.Op Fl s Ar sep +.Ek +.Bk -words +.Op Fl v Ar startnum +.Ek +.Bk -words +.Op Fl w Ar width +.Ek +.Op Ar file +.Sh DESCRIPTION +The +.Nm +utility reads lines from the named +.Ar file +or the standard input if the +.Ar file +argument is ommitted, +applies a configurable line numbering filter operation and writes the result +to the standard output. +.Pp +The +.Nm +utility treats the text it reads in terms of logical pages. +Unless specified otherwise, line numbering is reset at the start of each +logical page. A logical page consists of a header, a body and a footer +section; empty sections are valid. Different line numbering options are +independently available for header, body and footer sections. +.Pp +The starts of logical page sections are signalled by input lines containing +nothing but one of the following sequences of delimiter characters: +.Pp +.Bd -unfilled -offset indent +.Bl -column "\e:\e:\e: " "header " +.Em "Line Start of" +.It \e:\e:\e: header +.It \e:\e: body +.It \e: footer +.El +.Ed +.Pp +If the input does not contain any logical page section signalling directives, +the text being read is assumed to consist of a single logical page body. +.Pp +The following options are available: +.Bl -tag -width indent +.It Fl b Ar type +Specify the logical page body lines to be numbered. +Recognized +.Ar type +arguments are: +.Bl -tag -width pstringXX +.It a +Number all lines. +.It t +Number only non-empty lines. +.It n +No line numbering. +.It p Ns Ar expr +Number only those lines that contain the basic regular expression specified +by +.Ar expr . +.El +.Pp +The default +.Ar type +for logical page body lines is t. +.It Fl d Ar delim +Specify the delimiter characters used to indicate the start of a logical +page section in the input file. At most two characters may be specified; +if only one character is specified, the first character is replaced and the +second character remains unchanged. +The default +.Ar delim +characters are ``\e:''. +.It Fl f Ar type +Specify the same as +.Fl b Ar type +except for logical page footer lines. +The default +.Ar type +for logical page footer lines is n. +.It Fl h Ar type +Specify the same as +.Fl b Ar type +except for logical page header lines. +The default +.Ar type +for logical page header lines is n. +.It Fl i Ar incr +Specify the increment value used to number logical page lines. +The default +.Ar incr +value is 1. +.It Fl l Ar num +If numbering of all lines is specified for the current logical section +using the corresponding +.Fl b +a, +.Fl f +a +or +.Fl h +a +option, +specify the number of adjacent blank lines to be considered as one. +For example, +.Fl l +2 results in only the second adjacent blank line being numbered. +The default +.Ar num +value is 1. +.It Fl n Ar format +Specify the line numbering output format. +Recognized +.Ar format +arguments are: +.Bl -tag -width lnXX -compact +.It ln +Left justified. +.It rn +Right justified, leading zeros suppressed. +.It rz +Right justified, leading zeros kept. +.El +.Pp +The default +.Ar format +is rn. +.It Fl p +Specify that line numbering should not be restarted at logical page delimiters. +.It Fl s Ar sep +Specify the characters used in separating the line number and the corresponding +text line. +The default +.Ar sep +setting is a single tab character. +.It Fl v Ar startnum +Specify the initial value used to number logical page lines; see also the +description of the +.Fl p +option. +The default +.Ar startnum +value is 1. +.It Fl w Ar width +Specify the number of characters to be occupied by the line number; +in case the +.Ar width +is insufficient to hold the line number, it will be truncated to its +.Ar width +least significant digits. +The default +.Ar width +is 6. +.El +.Sh EXIT STATUS +The +.Nm +utility exits 0 on success, and >0 if an error occurs. +.Sh SEE ALSO +.Xr pr 1 +.Sh STANDARDS +The +.Nm +utility conforms to +.St -xpg4.2 +with the exception of not supporting the intermingling of the +.Ar file +operand with the options, which the standard considers an obsolscent feature +to be removed from a further issue. +.Sh HISTORY +The +.Nm +utility first appeared in +.At V.2 . diff --git a/usr.bin/nl/nl.c b/usr.bin/nl/nl.c new file mode 100644 index 000000000000..64799e4c760b --- /dev/null +++ b/usr.bin/nl/nl.c @@ -0,0 +1,444 @@ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 1999 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Klaus Klein. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT( +"@(#) Copyright (c) 1999\ + The NetBSD Foundation, Inc. All rights reserved."); +__RCSID("$FreeBSD$"); +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +typedef enum { + number_all, /* number all lines */ + number_nonempty, /* number non-empty lines */ + number_none, /* no line numbering */ + number_regex /* number lines matching regular expression */ +} numbering_type; + +struct numbering_property { + const char * const name; /* for diagnostics */ + numbering_type type; /* numbering type */ + regex_t expr; /* for type == number_regex */ +}; + +/* line numbering formats */ +#define FORMAT_LN "%-*d" /* left justified, leading zeros suppressed */ +#define FORMAT_RN "%*d" /* right justified, leading zeros suppressed */ +#define FORMAT_RZ "%0*d" /* right justified, leading zeros kept */ + +#define FOOTER 0 +#define BODY 1 +#define HEADER 2 +#define NP_LAST HEADER + +static struct numbering_property numbering_properties[NP_LAST + 1] = { + { "footer", number_none }, + { "body", number_nonempty }, + { "header", number_none } +}; + +#define max(a, b) ((a) > (b) ? (a) : (b)) + +/* + * Maximum number of characters required for a decimal representation of a + * (signed) int; courtesy of tzcode. + */ +#define INT_STRLEN_MAXIMUM \ + ((sizeof (int) * CHAR_BIT - 1) * 302 / 1000 + 2) + +static void filter __P((void)); +int main __P((int, char *[])); +static void parse_numbering __P((const char *, int)); +static void usage __P((void)); + +/* + * Pointer to dynamically allocated input line buffer, and its size. + */ +static char *buffer; +static size_t buffersize; + +/* + * Dynamically allocated buffer suitable for string representation of ints. + */ +static char *intbuffer; + +/* + * Configurable parameters. + */ +/* delimiter characters that indicate the start of a logical page section */ +static char delim[2] = { '\\', ':' }; + +/* line numbering format */ +static const char *format = FORMAT_RN; + +/* increment value used to number logical page lines */ +static int incr = 1; + +/* number of adjacent blank lines to be considered (and numbered) as one */ +static unsigned int nblank = 1; + +/* whether to restart numbering at logical page delimiters */ +static int restart = 1; + +/* characters used in separating the line number and the corrsp. text line */ +static const char *sep = "\t"; + +/* initial value used to number logical page lines */ +static int startnum = 1; + +/* number of characters to be used for the line number */ +/* should be unsigned but required signed by `*' precision conversion */ +static int width = 6; + + +int +main(argc, argv) + int argc; + char *argv[]; +{ + int c; + long val; + unsigned long uval; + char *ep; + size_t intbuffersize; + + (void)setlocale(LC_ALL, ""); + + /* + * Note: this implementation strictly conforms to the XBD Utility + * Syntax Guidelines and does not permit the optional `file' operand + * to be intermingled with the options, which is defined in the + * XCU specification (Issue 5) but declared an obsolescent feature that + * will be removed from a future issue. It shouldn't matter, though. + */ + while ((c = getopt(argc, argv, "pb:d:f:h:i:l:n:s:v:w:")) != -1) { + switch (c) { + case 'p': + restart = 0; + break; + case 'b': + parse_numbering(optarg, BODY); + break; + case 'd': + if (optarg[0] != '\0') + delim[0] = optarg[0]; + if (optarg[1] != '\0') + delim[1] = optarg[1]; + /* at most two delimiter characters */ + if (optarg[2] != '\0') { + (void)fprintf(stderr, + "nl: invalid delim argument -- %s\n", + optarg); + exit(EXIT_FAILURE); + /* NOTREACHED */ + } + break; + case 'f': + parse_numbering(optarg, FOOTER); + break; + case 'h': + parse_numbering(optarg, HEADER); + break; + case 'i': + errno = 0; + val = strtol(optarg, &ep, 10); + if ((ep != NULL && *ep != '\0') || + ((val == LONG_MIN || val == LONG_MAX) && errno != 0)) { + (void)fprintf(stderr, + "invalid incr argument -- %s\n", optarg); + exit(EXIT_FAILURE); + } + incr = (int)val; + break; + case 'l': + errno = 0; + uval = strtoul(optarg, &ep, 10); + if ((ep != NULL && *ep != '\0') || + (uval == ULONG_MAX && errno != 0)) { + (void)fprintf(stderr, + "invalid num argument -- %s\n", optarg); + exit(EXIT_FAILURE); + } + nblank = (unsigned int)uval; + break; + case 'n': + if (strcmp(optarg, "ln") == 0) { + format = FORMAT_LN; + } else if (strcmp(optarg, "rn") == 0) { + format = FORMAT_RN; + } else if (strcmp(optarg, "rz") == 0) { + format = FORMAT_RZ; + } else { + (void)fprintf(stderr, + "nl: illegal format -- %s\n", optarg); + exit(EXIT_FAILURE); + } + break; + case 's': + sep = optarg; + break; + case 'v': + errno = 0; + val = strtol(optarg, &ep, 10); + if ((ep != NULL && *ep != '\0') || + ((val == LONG_MIN || val == LONG_MAX) && errno != 0)) { + (void)fprintf(stderr, + "invalid startnum value -- %s\n", optarg); + exit(EXIT_FAILURE); + } + startnum = (int)val; + break; + case 'w': + errno = 0; + val = strtol(optarg, &ep, 10); + if ((ep != NULL && *ep != '\0') || + ((val == LONG_MIN || val == LONG_MAX) && errno != 0)) { + (void)fprintf(stderr, + "invalid width value -- %s\n", optarg); + exit(EXIT_FAILURE); + } + width = (int)val; + if (!(width > 0)) { + (void)fprintf(stderr, + "nl: width argument must be > 0 -- %d\n", + width); + exit(EXIT_FAILURE); + } + break; + case '?': + default: + usage(); + /* NOTREACHED */ + } + } + argc -= optind; + argv += optind; + + switch (argc) { + case 0: + break; + case 1: + if (freopen(argv[0], "r", stdin) == NULL) { + perror(argv[0]); + exit(EXIT_FAILURE); + } + break; + default: + usage(); + /* NOTREACHED */ + } + + /* Determine the maximum input line length to operate on. */ + if ((val = sysconf(_SC_LINE_MAX)) == -1) /* ignore errno */ + val = LINE_MAX; + /* Allocate sufficient buffer space (including the terminating NUL). */ + buffersize = (size_t)val + 1; + if ((buffer = malloc(buffersize)) == NULL) { + perror("cannot allocate input line buffer"); + exit(EXIT_FAILURE); + } + + /* Allocate a buffer suitable for preformatting line number. */ + intbuffersize = max(INT_STRLEN_MAXIMUM, width) + 1; /* NUL */ + if ((intbuffer = malloc(intbuffersize)) == NULL) { + perror("cannot allocate preformatting buffer"); + exit(EXIT_FAILURE); + } + + /* Do the work. */ + filter(); + + exit(EXIT_SUCCESS); + /* NOTREACHED */ +} + +static void +filter() +{ + int line; /* logical line number */ + int section; /* logical page section */ + unsigned int adjblank; /* adjacent blank lines */ + int consumed; /* intbuffer measurement */ + int donumber, idx; + + adjblank = 0; + line = startnum; + section = BODY; +#ifdef __GNUC__ + (void)&donumber; /* avoid bogus `uninitialized' warning */ +#endif + + while (fgets(buffer, (int)buffersize, stdin) != NULL) { + for (idx = FOOTER; idx <= NP_LAST; idx++) { + /* Does it look like a delimiter? */ + if (buffer[2 * idx + 0] == delim[0] && + buffer[2 * idx + 1] == delim[1]) { + /* Was this the whole line? */ + if (buffer[2 * idx + 2] == '\n') { + section = idx; + adjblank = 0; + if (restart) + line = startnum; + goto nextline; + } + } else { + break; + } + } + + switch (numbering_properties[section].type) { + case number_all: + /* + * Doing this for number_all only is disputable, but + * the standard expresses an explicit dependency on + * `-b a' etc. + */ + if (buffer[0] == '\n' && ++adjblank < nblank) + donumber = 0; + else + donumber = 1, adjblank = 0; + break; + case number_nonempty: + donumber = (buffer[0] != '\n'); + break; + case number_none: + donumber = 0; + break; + case number_regex: + donumber = + (regexec(&numbering_properties[section].expr, + buffer, 0, NULL, 0) == 0); + break; + } + + if (donumber) { + /* Note: sprintf() is safe here. */ + consumed = sprintf(intbuffer, format, width, line); + (void)printf("%s", + intbuffer + max(0, consumed - width)); + line += incr; + } else { + (void)printf("%*s", width, ""); + } + (void)printf("%s%s", sep, buffer); + + if (ferror(stdout)) { + perror("output error"); + exit(EXIT_FAILURE); + } +nextline: + ; + } + + if (ferror(stdin)) { + perror("input error"); + exit(EXIT_FAILURE); + } +} + +/* + * Various support functions. + */ + +static void +parse_numbering(argstr, section) + const char *argstr; + int section; +{ + int error; + char errorbuf[NL_TEXTMAX]; + + switch (argstr[0]) { + case 'a': + numbering_properties[section].type = number_all; + break; + case 'n': + numbering_properties[section].type = number_none; + break; + case 't': + numbering_properties[section].type = number_nonempty; + break; + case 'p': + /* If there was a previous expression, throw it away. */ + if (numbering_properties[section].type == number_regex) + regfree(&numbering_properties[section].expr); + else + numbering_properties[section].type = number_regex; + + /* Compile/validate the supplied regular expression. */ + if ((error = regcomp(&numbering_properties[section].expr, + &argstr[1], REG_NEWLINE|REG_NOSUB)) != 0) { + (void)regerror(error, + &numbering_properties[section].expr, + errorbuf, sizeof (errorbuf)); + (void)fprintf(stderr, + "nl: %s expr: %s -- %s\n", + numbering_properties[section].name, errorbuf, + &argstr[1]); + exit(EXIT_FAILURE); + } + break; + default: + (void)fprintf(stderr, + "nl: illegal %s line numbering type -- %s\n", + numbering_properties[section].name, argstr); + exit(EXIT_FAILURE); + } +} + +static void +usage() +{ + + (void)fprintf(stderr, "usage: nl [-p] [-b type] [-d delim] [-f type] \ +[-h type] [-i incr] [-l num]\n\t[-n format] [-s sep] [-v startnum] [-w width] \ +[file]\n"); + exit(EXIT_FAILURE); +}