diff --git a/distrib/sets/lists/minix/mi b/distrib/sets/lists/minix/mi index a8a1af0a6..dbe8f36cb 100644 --- a/distrib/sets/lists/minix/mi +++ b/distrib/sets/lists/minix/mi @@ -406,6 +406,7 @@ ./usr/bin/nbperf minix-sys ./usr/bin/newgrp minix-sys ./usr/bin/nice minix-sys +./usr/bin/nl minix-sys ./usr/bin/nm minix-sys binutils ./usr/bin/nohup minix-sys ./usr/bin/nonamed minix-sys @@ -1975,6 +1976,7 @@ ./usr/man/man1/nbperf.1 minix-sys ./usr/man/man1/newgrp.1 minix-sys ./usr/man/man1/nice.1 minix-sys +./usr/man/man1/nl.1 minix-sys ./usr/man/man1/nm.1 minix-sys obsolete ./usr/man/man1/nohup.1 minix-sys ./usr/man/man1/objcopy.1 minix-sys binutils diff --git a/lib/libc/gen/minix/sysconf.c b/lib/libc/gen/minix/sysconf.c index 45a9e7b13..3a1e6fd24 100644 --- a/lib/libc/gen/minix/sysconf.c +++ b/lib/libc/gen/minix/sysconf.c @@ -75,6 +75,9 @@ int name; /* property being inspected */ case _SC_PAGESIZE: return getpagesize(); + case _SC_LINE_MAX: + return (long) LINE_MAX; + default: errno = EINVAL; return -1L; diff --git a/releasetools/nbsd_ports b/releasetools/nbsd_ports index 849d5556b..5149a83d5 100644 --- a/releasetools/nbsd_ports +++ b/releasetools/nbsd_ports @@ -179,6 +179,7 @@ 2009/08/15 20:44:56,usr.bin/mktemp 2012/10/17 12:00:00,usr.bin/nbperf 2013/10/10 12:00:00,usr.bin/nice +2013/10/13 12:00:00,usr.bin/nl 2013/10/12 12:00:00,usr.bin/nohup 2012/10/17 12:00:00,usr.bin/nvi 2010/05/14 17:28:23,usr.bin/newgrp diff --git a/sys/sys/unistd.h b/sys/sys/unistd.h index b0f3c31a7..d24c3b313 100644 --- a/sys/sys/unistd.h +++ b/sys/sys/unistd.h @@ -204,5 +204,6 @@ #define _SC_TZNAME_MAX 10 #define _SC_PAGESIZE 11 #define _SC_PAGE_SIZE _SC_PAGESIZE +#define _SC_LINE_MAX 15 #endif /* !_SYS_UNISTD_H_ */ diff --git a/usr.bin/Makefile b/usr.bin/Makefile index ff1d9df62..aa7aa88c7 100644 --- a/usr.bin/Makefile +++ b/usr.bin/Makefile @@ -18,7 +18,7 @@ SUBDIR= \ machine make man \ mkdep mktemp \ \ - nbperf newgrp nice nohup nvi \ + nbperf newgrp nice nl nohup nvi \ passwd \ printf \ rev \ diff --git a/usr.bin/nl/Makefile b/usr.bin/nl/Makefile new file mode 100644 index 000000000..51c48937f --- /dev/null +++ b/usr.bin/nl/Makefile @@ -0,0 +1,7 @@ +# $NetBSD: Makefile,v 1.4 2011/08/16 12:00:46 christos Exp $ + +PROG= nl + +COPTS.nl.c += -Wno-format-nonliteral + +.include diff --git a/usr.bin/nl/nl.1 b/usr.bin/nl/nl.1 new file mode 100644 index 000000000..d7953907a --- /dev/null +++ b/usr.bin/nl/nl.1 @@ -0,0 +1,214 @@ +.\" $NetBSD: nl.1,v 1.16 2013/09/24 22:08:06 wiz Exp $ +.\" +.\" 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. +.\" +.\" 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 May 1, 2013 +.Dt NL 1 +.Os +.Sh NAME +.Nm nl +.Nd line numbering filter +.Sh SYNOPSIS +.Nm +.Op Fl p +.Op Fl b Ar type +.Op Fl d Ar delim +.Op Fl f Ar type +.Op Fl h Ar type +.Op Fl i Ar incr +.Op Fl l Ar num +.Op Fl n Ar format +.Op Fl s Ar sep +.Op Fl v Ar startnum +.Op Fl w Ar width +.Op Ar file +.Sh DESCRIPTION +The +.Nm +utility reads lines from the named +.Ar file , +applies a configurable line numbering filter operation, +and writes the result to the standard output. +If +.Ar file +is a single dash +.Pq Sq \&- +or absent, +.Nm +reads from the standard input. +.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 signaled by input lines containing +nothing but one of the following sequences of delimiter characters: +.Bl -column "\e:\e:\e: " "header " -offset indent +.It Em "Line" Ta Em "Start of" +.It \e:\e:\e: header +.It \e:\e: body +.It \e: footer +.El +.Pp +If the input does not contain any logical page section signaling 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: +.Pp +.Bl -tag -width lnXX -compact -offset indent +.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; +if 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 +.Ex -std +.Sh SEE ALSO +.Xr pr 1 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -xpg4.2 +specification. +.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 000000000..14054ad98 --- /dev/null +++ b/usr.bin/nl/nl.c @@ -0,0 +1,406 @@ +/* $NetBSD: nl.c,v 1.12 2013/09/17 20:00:50 wiz Exp $ */ + +/*- + * 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. + * + * 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("$NetBSD: nl.c,v 1.12 2013/09/17 20:00:50 wiz Exp $"); +#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, { 0, 0, 0, 0 } }, + { "body", number_nonempty, { 0, 0, 0, 0 } }, + { "header", number_none, { 0, 0, 0, 0 } }, +}; + +#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(void); +static void parse_numbering(const char *, int); +static void usage(void) __attribute__((__noreturn__)); + +/* + * 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; +static size_t intbuffersize; + +/* + * 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(int argc, char *argv[]) +{ + int c; + long val; + unsigned long uval; + char *ep; + + (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') { + errx(EXIT_FAILURE, + "invalid delim argument -- %s", + optarg); + /* 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)) + errx(EXIT_FAILURE, + "invalid incr argument -- %s", optarg); + incr = (int)val; + break; + case 'l': + errno = 0; + uval = strtoul(optarg, &ep, 10); + if ((ep != NULL && *ep != '\0') || + (uval == ULONG_MAX && errno != 0)) + errx(EXIT_FAILURE, + "invalid num argument -- %s", optarg); + 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 + errx(EXIT_FAILURE, + "illegal format -- %s", optarg); + 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)) + errx(EXIT_FAILURE, + "invalid startnum value -- %s", optarg); + 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)) + errx(EXIT_FAILURE, + "invalid width value -- %s", optarg); + width = (int)val; + if (!(width > 0)) + errx(EXIT_FAILURE, + "width argument must be > 0 -- %d", + width); + break; + case '?': + default: + usage(); + /* NOTREACHED */ + } + } + argc -= optind; + argv += optind; + + switch (argc) { + case 0: + break; + case 1: + if (strcmp(argv[0], "-") != 0 && + freopen(argv[0], "r", stdin) == NULL) + err(EXIT_FAILURE, "Cannot open `%s'", argv[0]); + 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) + err(EXIT_FAILURE, "Cannot allocate input line buffer"); + + /* Allocate a buffer suitable for preformatting line number. */ + intbuffersize = max((int)INT_STRLEN_MAXIMUM, width) + 1; /* NUL */ + if ((intbuffer = malloc(intbuffersize)) == NULL) + err(EXIT_FAILURE, "cannot allocate preformatting buffer"); + + /* Do the work. */ + filter(); + + return EXIT_SUCCESS; + /* NOTREACHED */ +} + +static void +filter(void) +{ + 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__ + donumber = 0; /* 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) { + consumed = snprintf(intbuffer, intbuffersize, 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)) + err(EXIT_FAILURE, "output error"); +nextline: + ; + } + + if (ferror(stdin)) + err(EXIT_FAILURE, "input error"); +} + +/* + * Various support functions. + */ + +static void +parse_numbering(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)); + errx(EXIT_FAILURE, + "%s expr: %s -- %s", + numbering_properties[section].name, errorbuf, + &argstr[1]); + } + break; + default: + errx(EXIT_FAILURE, + "illegal %s line numbering type -- %s", + numbering_properties[section].name, argstr); + exit(EXIT_FAILURE); + } +} + +static void +usage(void) +{ + (void)fprintf(stderr, "Usage: %s [-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", getprogname()); + exit(EXIT_FAILURE); +}