nl.c revision 97337
176273Sbrian/*- 276273Sbrian * Copyright (c) 1999 The NetBSD Foundation, Inc. 376273Sbrian * All rights reserved. 476273Sbrian * 576273Sbrian * This code is derived from software contributed to The NetBSD Foundation 676273Sbrian * by Klaus Klein. 776273Sbrian * 876273Sbrian * Redistribution and use in source and binary forms, with or without 976273Sbrian * modification, are permitted provided that the following conditions 1076273Sbrian * are met: 1176273Sbrian * 1. Redistributions of source code must retain the above copyright 1276273Sbrian * notice, this list of conditions and the following disclaimer. 1376273Sbrian * 2. Redistributions in binary form must reproduce the above copyright 1476273Sbrian * notice, this list of conditions and the following disclaimer in the 1576273Sbrian * documentation and/or other materials provided with the distribution. 1676273Sbrian * 3. All advertising materials mentioning features or use of this software 1776273Sbrian * must display the following acknowledgement: 1876273Sbrian * This product includes software developed by the NetBSD 1976273Sbrian * Foundation, Inc. and its contributors. 2076273Sbrian * 4. Neither the name of The NetBSD Foundation nor the names of its 2176273Sbrian * contributors may be used to endorse or promote products derived 2276273Sbrian * from this software without specific prior written permission. 2376273Sbrian * 2476273Sbrian * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 2576273Sbrian * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 2676273Sbrian * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 2776273Sbrian * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 2876273Sbrian * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 2976273Sbrian * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 3076273Sbrian * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 3176273Sbrian * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 3276273Sbrian * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 3376273Sbrian * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 3476273Sbrian * POSSIBILITY OF SUCH DAMAGE. 3576273Sbrian */ 3676273Sbrian 3776273Sbrian#include <sys/cdefs.h> 3876273Sbrian#ifndef lint 3976273Sbrian__COPYRIGHT( 4076273Sbrian"@(#) Copyright (c) 1999\ 4176273Sbrian The NetBSD Foundation, Inc. All rights reserved."); 4276273Sbrian__RCSID("$FreeBSD: head/usr.bin/nl/nl.c 97337 2002-05-27 06:37:34Z tjr $"); 4376273Sbrian#endif 4476273Sbrian 4576273Sbrian#include <sys/types.h> 4676273Sbrian 4797337Stjr#include <err.h> 4876273Sbrian#include <errno.h> 4976273Sbrian#include <limits.h> 5076273Sbrian#include <locale.h> 5176273Sbrian#include <regex.h> 5276273Sbrian#include <stdio.h> 5376273Sbrian#include <stdlib.h> 5476273Sbrian#include <string.h> 5576273Sbrian#include <unistd.h> 5676273Sbrian 5776273Sbriantypedef enum { 5876273Sbrian number_all, /* number all lines */ 5976273Sbrian number_nonempty, /* number non-empty lines */ 6076273Sbrian number_none, /* no line numbering */ 6176273Sbrian number_regex /* number lines matching regular expression */ 6276273Sbrian} numbering_type; 6376273Sbrian 6476273Sbrianstruct numbering_property { 6576273Sbrian const char * const name; /* for diagnostics */ 6676273Sbrian numbering_type type; /* numbering type */ 6776273Sbrian regex_t expr; /* for type == number_regex */ 6876273Sbrian}; 6976273Sbrian 7076273Sbrian/* line numbering formats */ 7176273Sbrian#define FORMAT_LN "%-*d" /* left justified, leading zeros suppressed */ 7276273Sbrian#define FORMAT_RN "%*d" /* right justified, leading zeros suppressed */ 7376273Sbrian#define FORMAT_RZ "%0*d" /* right justified, leading zeros kept */ 7476273Sbrian 7576273Sbrian#define FOOTER 0 7676273Sbrian#define BODY 1 7776273Sbrian#define HEADER 2 7876273Sbrian#define NP_LAST HEADER 7976273Sbrian 8076273Sbrianstatic struct numbering_property numbering_properties[NP_LAST + 1] = { 8176273Sbrian { "footer", number_none }, 8276273Sbrian { "body", number_nonempty }, 8376273Sbrian { "header", number_none } 8476273Sbrian}; 8576273Sbrian 8676273Sbrian#define max(a, b) ((a) > (b) ? (a) : (b)) 8776273Sbrian 8876273Sbrian/* 8976273Sbrian * Maximum number of characters required for a decimal representation of a 9076273Sbrian * (signed) int; courtesy of tzcode. 9176273Sbrian */ 9276273Sbrian#define INT_STRLEN_MAXIMUM \ 9376273Sbrian ((sizeof (int) * CHAR_BIT - 1) * 302 / 1000 + 2) 9476273Sbrian 9592921Simpstatic void filter(void); 9692921Simpint main(int, char *[]); 9792921Simpstatic void parse_numbering(const char *, int); 9892921Simpstatic void usage(void); 9976273Sbrian 10076273Sbrian/* 10176273Sbrian * Pointer to dynamically allocated input line buffer, and its size. 10276273Sbrian */ 10376273Sbrianstatic char *buffer; 10476273Sbrianstatic size_t buffersize; 10576273Sbrian 10676273Sbrian/* 10776273Sbrian * Dynamically allocated buffer suitable for string representation of ints. 10876273Sbrian */ 10976273Sbrianstatic char *intbuffer; 11076273Sbrian 11176273Sbrian/* 11276273Sbrian * Configurable parameters. 11376273Sbrian */ 11476273Sbrian/* delimiter characters that indicate the start of a logical page section */ 11576273Sbrianstatic char delim[2] = { '\\', ':' }; 11676273Sbrian 11776273Sbrian/* line numbering format */ 11876273Sbrianstatic const char *format = FORMAT_RN; 11976273Sbrian 12076273Sbrian/* increment value used to number logical page lines */ 12176273Sbrianstatic int incr = 1; 12276273Sbrian 12376273Sbrian/* number of adjacent blank lines to be considered (and numbered) as one */ 12476273Sbrianstatic unsigned int nblank = 1; 12576273Sbrian 12676273Sbrian/* whether to restart numbering at logical page delimiters */ 12776273Sbrianstatic int restart = 1; 12876273Sbrian 12976273Sbrian/* characters used in separating the line number and the corrsp. text line */ 13076273Sbrianstatic const char *sep = "\t"; 13176273Sbrian 13276273Sbrian/* initial value used to number logical page lines */ 13376273Sbrianstatic int startnum = 1; 13476273Sbrian 13576273Sbrian/* number of characters to be used for the line number */ 13676273Sbrian/* should be unsigned but required signed by `*' precision conversion */ 13776273Sbrianstatic int width = 6; 13876273Sbrian 13976273Sbrian 14076273Sbrianint 14176273Sbrianmain(argc, argv) 14276273Sbrian int argc; 14376273Sbrian char *argv[]; 14476273Sbrian{ 14576273Sbrian int c; 14676273Sbrian long val; 14776273Sbrian unsigned long uval; 14876273Sbrian char *ep; 14976273Sbrian size_t intbuffersize; 15076273Sbrian 15176273Sbrian (void)setlocale(LC_ALL, ""); 15276273Sbrian 15376273Sbrian /* 15476273Sbrian * Note: this implementation strictly conforms to the XBD Utility 15576273Sbrian * Syntax Guidelines and does not permit the optional `file' operand 15676273Sbrian * to be intermingled with the options, which is defined in the 15776273Sbrian * XCU specification (Issue 5) but declared an obsolescent feature that 15876273Sbrian * will be removed from a future issue. It shouldn't matter, though. 15976273Sbrian */ 16076273Sbrian while ((c = getopt(argc, argv, "pb:d:f:h:i:l:n:s:v:w:")) != -1) { 16176273Sbrian switch (c) { 16276273Sbrian case 'p': 16376273Sbrian restart = 0; 16476273Sbrian break; 16576273Sbrian case 'b': 16676273Sbrian parse_numbering(optarg, BODY); 16776273Sbrian break; 16876273Sbrian case 'd': 16976273Sbrian if (optarg[0] != '\0') 17076273Sbrian delim[0] = optarg[0]; 17176273Sbrian if (optarg[1] != '\0') 17276273Sbrian delim[1] = optarg[1]; 17376273Sbrian /* at most two delimiter characters */ 17476273Sbrian if (optarg[2] != '\0') { 17576273Sbrian (void)fprintf(stderr, 17676273Sbrian "nl: invalid delim argument -- %s\n", 17776273Sbrian optarg); 17876273Sbrian exit(EXIT_FAILURE); 17976273Sbrian /* NOTREACHED */ 18076273Sbrian } 18176273Sbrian break; 18276273Sbrian case 'f': 18376273Sbrian parse_numbering(optarg, FOOTER); 18476273Sbrian break; 18576273Sbrian case 'h': 18676273Sbrian parse_numbering(optarg, HEADER); 18776273Sbrian break; 18876273Sbrian case 'i': 18976273Sbrian errno = 0; 19076273Sbrian val = strtol(optarg, &ep, 10); 19176273Sbrian if ((ep != NULL && *ep != '\0') || 19276273Sbrian ((val == LONG_MIN || val == LONG_MAX) && errno != 0)) { 19376273Sbrian (void)fprintf(stderr, 19476273Sbrian "invalid incr argument -- %s\n", optarg); 19576273Sbrian exit(EXIT_FAILURE); 19676273Sbrian } 19776273Sbrian incr = (int)val; 19876273Sbrian break; 19976273Sbrian case 'l': 20076273Sbrian errno = 0; 20176273Sbrian uval = strtoul(optarg, &ep, 10); 20276273Sbrian if ((ep != NULL && *ep != '\0') || 20376273Sbrian (uval == ULONG_MAX && errno != 0)) { 20476273Sbrian (void)fprintf(stderr, 20576273Sbrian "invalid num argument -- %s\n", optarg); 20676273Sbrian exit(EXIT_FAILURE); 20776273Sbrian } 20876273Sbrian nblank = (unsigned int)uval; 20976273Sbrian break; 21076273Sbrian case 'n': 21176273Sbrian if (strcmp(optarg, "ln") == 0) { 21276273Sbrian format = FORMAT_LN; 21376273Sbrian } else if (strcmp(optarg, "rn") == 0) { 21476273Sbrian format = FORMAT_RN; 21576273Sbrian } else if (strcmp(optarg, "rz") == 0) { 21676273Sbrian format = FORMAT_RZ; 21776273Sbrian } else { 21876273Sbrian (void)fprintf(stderr, 21976273Sbrian "nl: illegal format -- %s\n", optarg); 22076273Sbrian exit(EXIT_FAILURE); 22176273Sbrian } 22276273Sbrian break; 22376273Sbrian case 's': 22476273Sbrian sep = optarg; 22576273Sbrian break; 22676273Sbrian case 'v': 22776273Sbrian errno = 0; 22876273Sbrian val = strtol(optarg, &ep, 10); 22976273Sbrian if ((ep != NULL && *ep != '\0') || 23076273Sbrian ((val == LONG_MIN || val == LONG_MAX) && errno != 0)) { 23176273Sbrian (void)fprintf(stderr, 23276273Sbrian "invalid startnum value -- %s\n", optarg); 23376273Sbrian exit(EXIT_FAILURE); 23476273Sbrian } 23576273Sbrian startnum = (int)val; 23676273Sbrian break; 23776273Sbrian case 'w': 23876273Sbrian errno = 0; 23976273Sbrian val = strtol(optarg, &ep, 10); 24076273Sbrian if ((ep != NULL && *ep != '\0') || 24176273Sbrian ((val == LONG_MIN || val == LONG_MAX) && errno != 0)) { 24276273Sbrian (void)fprintf(stderr, 24376273Sbrian "invalid width value -- %s\n", optarg); 24476273Sbrian exit(EXIT_FAILURE); 24576273Sbrian } 24676273Sbrian width = (int)val; 24776273Sbrian if (!(width > 0)) { 24876273Sbrian (void)fprintf(stderr, 24976273Sbrian "nl: width argument must be > 0 -- %d\n", 25076273Sbrian width); 25176273Sbrian exit(EXIT_FAILURE); 25276273Sbrian } 25376273Sbrian break; 25476273Sbrian case '?': 25576273Sbrian default: 25676273Sbrian usage(); 25776273Sbrian /* NOTREACHED */ 25876273Sbrian } 25976273Sbrian } 26076273Sbrian argc -= optind; 26176273Sbrian argv += optind; 26276273Sbrian 26376273Sbrian switch (argc) { 26476273Sbrian case 0: 26576273Sbrian break; 26676273Sbrian case 1: 26797337Stjr if (freopen(argv[0], "r", stdin) == NULL) 26897337Stjr err(EXIT_FAILURE, "%s", argv[0]); 26976273Sbrian break; 27076273Sbrian default: 27176273Sbrian usage(); 27276273Sbrian /* NOTREACHED */ 27376273Sbrian } 27476273Sbrian 27576273Sbrian /* Determine the maximum input line length to operate on. */ 27676273Sbrian if ((val = sysconf(_SC_LINE_MAX)) == -1) /* ignore errno */ 27776273Sbrian val = LINE_MAX; 27876273Sbrian /* Allocate sufficient buffer space (including the terminating NUL). */ 27976273Sbrian buffersize = (size_t)val + 1; 28097337Stjr if ((buffer = malloc(buffersize)) == NULL) 28197337Stjr err(EXIT_FAILURE, "cannot allocate input line buffer"); 28276273Sbrian 28376273Sbrian /* Allocate a buffer suitable for preformatting line number. */ 28476273Sbrian intbuffersize = max(INT_STRLEN_MAXIMUM, width) + 1; /* NUL */ 28597337Stjr if ((intbuffer = malloc(intbuffersize)) == NULL) 28697337Stjr err(EXIT_FAILURE, "cannot allocate preformatting buffer"); 28776273Sbrian 28876273Sbrian /* Do the work. */ 28976273Sbrian filter(); 29076273Sbrian 29176273Sbrian exit(EXIT_SUCCESS); 29276273Sbrian /* NOTREACHED */ 29376273Sbrian} 29476273Sbrian 29576273Sbrianstatic void 29676273Sbrianfilter() 29776273Sbrian{ 29876273Sbrian int line; /* logical line number */ 29976273Sbrian int section; /* logical page section */ 30076273Sbrian unsigned int adjblank; /* adjacent blank lines */ 30176273Sbrian int consumed; /* intbuffer measurement */ 30276273Sbrian int donumber, idx; 30376273Sbrian 30476273Sbrian adjblank = 0; 30576273Sbrian line = startnum; 30676273Sbrian section = BODY; 30776273Sbrian#ifdef __GNUC__ 30876273Sbrian (void)&donumber; /* avoid bogus `uninitialized' warning */ 30976273Sbrian#endif 31076273Sbrian 31176273Sbrian while (fgets(buffer, (int)buffersize, stdin) != NULL) { 31276273Sbrian for (idx = FOOTER; idx <= NP_LAST; idx++) { 31376273Sbrian /* Does it look like a delimiter? */ 31476273Sbrian if (buffer[2 * idx + 0] == delim[0] && 31576273Sbrian buffer[2 * idx + 1] == delim[1]) { 31676273Sbrian /* Was this the whole line? */ 31776273Sbrian if (buffer[2 * idx + 2] == '\n') { 31876273Sbrian section = idx; 31976273Sbrian adjblank = 0; 32076273Sbrian if (restart) 32176273Sbrian line = startnum; 32276273Sbrian goto nextline; 32376273Sbrian } 32476273Sbrian } else { 32576273Sbrian break; 32676273Sbrian } 32776273Sbrian } 32876273Sbrian 32976273Sbrian switch (numbering_properties[section].type) { 33076273Sbrian case number_all: 33176273Sbrian /* 33276273Sbrian * Doing this for number_all only is disputable, but 33376273Sbrian * the standard expresses an explicit dependency on 33476273Sbrian * `-b a' etc. 33576273Sbrian */ 33676273Sbrian if (buffer[0] == '\n' && ++adjblank < nblank) 33776273Sbrian donumber = 0; 33876273Sbrian else 33976273Sbrian donumber = 1, adjblank = 0; 34076273Sbrian break; 34176273Sbrian case number_nonempty: 34276273Sbrian donumber = (buffer[0] != '\n'); 34376273Sbrian break; 34476273Sbrian case number_none: 34576273Sbrian donumber = 0; 34676273Sbrian break; 34776273Sbrian case number_regex: 34876273Sbrian donumber = 34976273Sbrian (regexec(&numbering_properties[section].expr, 35076273Sbrian buffer, 0, NULL, 0) == 0); 35176273Sbrian break; 35276273Sbrian } 35376273Sbrian 35476273Sbrian if (donumber) { 35576273Sbrian /* Note: sprintf() is safe here. */ 35676273Sbrian consumed = sprintf(intbuffer, format, width, line); 35776273Sbrian (void)printf("%s", 35876273Sbrian intbuffer + max(0, consumed - width)); 35976273Sbrian line += incr; 36076273Sbrian } else { 36176273Sbrian (void)printf("%*s", width, ""); 36276273Sbrian } 36376273Sbrian (void)printf("%s%s", sep, buffer); 36476273Sbrian 36597337Stjr if (ferror(stdout)) 36697337Stjr err(EXIT_FAILURE, "output error"); 36776273Sbriannextline: 36876273Sbrian ; 36976273Sbrian } 37076273Sbrian 37197337Stjr if (ferror(stdin)) 37297337Stjr err(EXIT_FAILURE, "input error"); 37376273Sbrian} 37476273Sbrian 37576273Sbrian/* 37676273Sbrian * Various support functions. 37776273Sbrian */ 37876273Sbrian 37976273Sbrianstatic void 38076273Sbrianparse_numbering(argstr, section) 38176273Sbrian const char *argstr; 38276273Sbrian int section; 38376273Sbrian{ 38476273Sbrian int error; 38576273Sbrian char errorbuf[NL_TEXTMAX]; 38676273Sbrian 38776273Sbrian switch (argstr[0]) { 38876273Sbrian case 'a': 38976273Sbrian numbering_properties[section].type = number_all; 39076273Sbrian break; 39176273Sbrian case 'n': 39276273Sbrian numbering_properties[section].type = number_none; 39376273Sbrian break; 39476273Sbrian case 't': 39576273Sbrian numbering_properties[section].type = number_nonempty; 39676273Sbrian break; 39776273Sbrian case 'p': 39876273Sbrian /* If there was a previous expression, throw it away. */ 39976273Sbrian if (numbering_properties[section].type == number_regex) 40076273Sbrian regfree(&numbering_properties[section].expr); 40176273Sbrian else 40276273Sbrian numbering_properties[section].type = number_regex; 40376273Sbrian 40476273Sbrian /* Compile/validate the supplied regular expression. */ 40576273Sbrian if ((error = regcomp(&numbering_properties[section].expr, 40676273Sbrian &argstr[1], REG_NEWLINE|REG_NOSUB)) != 0) { 40776273Sbrian (void)regerror(error, 40876273Sbrian &numbering_properties[section].expr, 40976273Sbrian errorbuf, sizeof (errorbuf)); 41076273Sbrian (void)fprintf(stderr, 41176273Sbrian "nl: %s expr: %s -- %s\n", 41276273Sbrian numbering_properties[section].name, errorbuf, 41376273Sbrian &argstr[1]); 41476273Sbrian exit(EXIT_FAILURE); 41576273Sbrian } 41676273Sbrian break; 41776273Sbrian default: 41876273Sbrian (void)fprintf(stderr, 41976273Sbrian "nl: illegal %s line numbering type -- %s\n", 42076273Sbrian numbering_properties[section].name, argstr); 42176273Sbrian exit(EXIT_FAILURE); 42276273Sbrian } 42376273Sbrian} 42476273Sbrian 42576273Sbrianstatic void 42676273Sbrianusage() 42776273Sbrian{ 42876273Sbrian 42976273Sbrian (void)fprintf(stderr, "usage: nl [-p] [-b type] [-d delim] [-f type] \ 43076273Sbrian[-h type] [-i incr] [-l num]\n\t[-n format] [-s sep] [-v startnum] [-w width] \ 43176273Sbrian[file]\n"); 43276273Sbrian exit(EXIT_FAILURE); 43376273Sbrian} 434