Audit nl(1)

1) Refactor the manpage to use the num-syntax and concise wording.
2) Build format instead of having a list of static strings.
3) BUGFIX: if (!buf[0] || buf[0] == '\n') Process last-read-line
           properly.
4) BUGFIX: In case we hit a formatting line, print a newline instead
           of just dropping it.
5) Use a switch instead of having spaghetti-cases.
6) Don't use printf-magic but explicitly do a putchar(' ')-loop.
7) Update usage(), indent properly.
8) BUGFIX: strchr is not NULL when type[0] is \0. Check for \0
           separately beforehand.
9) Reorder arg.h-cases for better readability.
This commit is contained in:
FRIGN 2015-03-22 16:29:50 +01:00
parent d49f6f2044
commit 587575dcb4
3 changed files with 107 additions and 101 deletions

2
README
View File

@ -49,7 +49,7 @@ The following tools are implemented ('*' == finished, '#' == UTF-8 support,
=*| mktemp non-posix none =*| mktemp non-posix none
=*| mv yes none (-i) =*| mv yes none (-i)
=*| nice yes none =*| nice yes none
#* nl yes none #*| nl yes none
=*| nohup yes none =*| nohup yes none
#*| paste yes none #*| paste yes none
=*| printenv non-posix none =*| printenv non-posix none

82
nl.1
View File

@ -1,4 +1,4 @@
.Dd March 18, 2015 .Dd March 22, 2015
.Dt NL 1 .Dt NL 1
.Os sbase .Os sbase
.Sh NAME .Sh NAME
@ -11,18 +11,19 @@
.Op Fl d Ar delim .Op Fl d Ar delim
.Op Fl f Ar type .Op Fl f Ar type
.Op Fl h Ar type .Op Fl h Ar type
.Op Fl i Ar incr .Op Fl i Ar num
.Op Fl l Ar num .Op Fl l Ar num
.Op Fl n Ar format .Op Fl n Ar format
.Op Fl s Ar sep .Op Fl s Ar sep
.Op Fl v Ar startnum .Op Fl v Ar num
.Op Fl w Ar width .Op Fl w Ar num
.Op Ar file .Op Ar file
.Sh DESCRIPTION .Sh DESCRIPTION
.Nm .Nm
reads lines from the named reads lines from
.Ar file .Ar file
and writes them to stdout with non-empty lines numbered. If no and writes them to stdout, numbering non-empty lines.
If no
.Ar file .Ar file
is given is given
.Nm .Nm
@ -30,64 +31,61 @@ reads from stdin.
.Sh OPTIONS .Sh OPTIONS
.Bl -tag -width Ds .Bl -tag -width Ds
.It Fl p .It Fl p
Do not reset number for logical pages Do not reset line number for logical pages.
.It Fl b Ar type .It Fl h Ar type | Fl b Ar type | Fl f Ar type
Defines which lines will be numbered for body sections: Define which lines to number in the head | body | footer section:
.Bl -tag -width pstringXX .Bl -tag -width pstringXX
.It a .It a
All lines. All lines.
.It n .It n
No lines. No lines.
.It t .It t
Only non-empty lines (default). Only non-empty lines. This is the default.
.It p Ns Ar expr .It p Ns Ar expr
Only lines which match Only lines matching
.Ar expr , .Ar expr
a regular expression as defined in according to
.Xr regex 7 . .Xr regex 7 .
.El .El
.It Fl d Ar delim .It Fl d Ar delim
Specify the delimiter (default is "\\:"). If only one character is specified, the second remains ':'. Set
.It Fl f Ar type .Ar delim
Same as as the delimiter for logical pages. If
.Fl b .Ar delim
except for footer sections. is only on character,
.It Fl h Ar type .Nm
Same as appends ":" to it. The default is "\e:".
.Fl b .It Fl i Ar num
except for header sections. Set the increment between numbered lines to
.It Fl i Ar incr .Ar num .
Defines the increment between numbered lines.
.It Fl l Ar num .It Fl l Ar num
Specify the number of adjacent blank lines to be considered as one. Default is 1. Set the number of adjacent blank lines to be considered as one to
.Ar num .
The default is 1.
.It Fl n Ar format .It Fl n Ar format
Specify the line number output format. Set the line number output
The
.Ar format .Ar format
can be any of the following: to one of:
.Bl -tag -width pstringXX .Bl -tag -width pstringXX
.It ln .It ln
Left justified. Left justified.
.It rn .It rn
Right justified. Right justified. This is the default.
.It rz .It rz
Right justified with leading zeroes. Right justified with leading zeroes.
.El .El
.Pp .Pp
The default
.Ar format
is rn.
.It Fl s Ar sep .It Fl s Ar sep
Defines the string used to separate line numbers and lines. By default this is Use
a tab. .Ar sep
.It Fl v Ar startnum to separate line numbers and lines. The default is "\et".
Start counting from .It Fl v Ar num
.Ar startnum Start counting lines from
instead of the default 1. .Ar num .
.It Fl w Ar width The default is 1.
The number of characters to be occupied by the line number .It Fl w Ar num
will be set to Set the width of the line number to
.Ar width . .Ar num .
The default is 6. The default is 6.
.El .El
.Sh SEE ALSO .Sh SEE ALSO

124
nl.c
View File

@ -9,40 +9,33 @@
#include "utf.h" #include "utf.h"
#include "util.h" #include "util.h"
/* formats here specify line number and separator (not line content) */ static size_t startnum = 1;
#define FORMAT_LN "%-*ld%s" static size_t incr = 1;
#define FORMAT_RN "%*ld%s" static size_t blines = 1;
#define FORMAT_RZ "%0*ld%s" static size_t delimlen = 2;
static int width = 6;
static char type[] = { 'n', 't', 'n' }; /* footer, body, header */ static int pflag = 0;
static char *delim = "\\:"; static char type[] = { 'n', 't', 'n' }; /* footer, body, header */
static const char *format = FORMAT_RN; static char *delim = "\\:";
static const char *sep = "\t"; static char format[8] = "%*ld%s";
static int width = 6; static char *sep = "\t";
static int pflag = 0; static regex_t preg[3];
static size_t startnum = 1;
static size_t incr = 1;
static size_t blines = 1;
static size_t delimlen = 2;
static regex_t preg[3];
static int static int
getsection(char *buf, int *section) getsection(char *buf, int *section)
{ {
int sectionchanged = 0; int sectionchanged = 0, newsection = *section;
int newsection = *section;
for (; !strncmp(buf, delim, delimlen); buf += delimlen) { for (; !strncmp(buf, delim, delimlen); buf += delimlen) {
if (!sectionchanged) { if (!sectionchanged) {
sectionchanged = 1; sectionchanged = 1;
newsection = 0; newsection = 0;
} else { } else {
++newsection; newsection = (newsection + 1) % 3;
newsection %= 3;
} }
} }
if (buf && buf[0] == '\n') if (!buf[0] || buf[0] == '\n')
*section = newsection; *section = newsection;
else else
sectionchanged = 0; sectionchanged = 0;
@ -51,27 +44,33 @@ getsection(char *buf, int *section)
} }
static void static void
nl(const char *name, FILE *fp) nl(const char *fname, FILE *fp)
{ {
char *buf = NULL; size_t i, number = startnum, size = 0;
int donumber, oldsection, section = 1, bl = 1; int donumber, oldsection, section = 1, bl = 1;
size_t number = startnum, size = 0; char *buf = NULL;
while (getline(&buf, &size, fp) != -1) { while (getline(&buf, &size, fp) >= 0) {
donumber = 0; donumber = 0;
oldsection = section; oldsection = section;
if (getsection(buf, &section)) { if (getsection(buf, &section)) {
if ((section >= oldsection) && !pflag) if ((section >= oldsection) && !pflag)
number = startnum; number = startnum;
putchar('\n');
continue; continue;
} }
if ((type[section] == 't' && buf[0] != '\n') switch (type[section]) {
|| (type[section] == 'p' && case 't':
!regexec(&preg[section], buf, 0, NULL, 0))) { if (buf[0] != '\n')
donumber = 1; donumber = 1;
} else if (type[section] == 'a') { break;
case 'p':
if (!regexec(preg + section, buf, 0, NULL, 0))
donumber = 1;
break;
case 'a':
if (buf[0] == '\n' && bl < blines) { if (buf[0] == '\n' && bl < blines) {
++bl; ++bl;
} else { } else {
@ -84,28 +83,30 @@ nl(const char *name, FILE *fp)
printf(format, width, number, sep); printf(format, width, number, sep);
number += incr; number += incr;
} else { } else {
printf("%*s", width, ""); for (i = 0; i < width; i++)
putchar(' ');
} }
printf("%s", buf); fputs(buf, stdout);
} }
free(buf); free(buf);
if (ferror(fp)) if (ferror(fp))
eprintf("%s: read error:", name); eprintf("getline %s:", fname);
} }
static void static void
usage(void) usage(void)
{ {
eprintf("usage: %s [-p] [-b type] [-d delim] [-f type] " eprintf("usage: %s [-p] [-b type] [-d delim] [-f type]\n"
"[-h type] [-i incr] [-l num]\n[-n format] [-s sep] " " [-h type] [-i num] [-l num] [-n format]\n"
"[-v startnum] [-w width] [file]\n", argv0); " [-s sep] [-v num] [-w num] [file]\n", argv0);
} }
static char static char
getlinetype(char *type, regex_t *preg) getlinetype(char *type, regex_t *preg)
{ {
if (type[0] == 'p') if (type[0] == 'p')
eregcomp(preg, &type[1], REG_NOSUB); eregcomp(preg, type + 1, REG_NOSUB);
else if (!strchr("ant", type[0])) else if (!type[0] || !strchr("ant", type[0]))
usage(); usage();
return type[0]; return type[0];
@ -115,13 +116,10 @@ int
main(int argc, char *argv[]) main(int argc, char *argv[])
{ {
FILE *fp; FILE *fp;
char *d;
size_t l, s; size_t l, s;
char *d, *formattype, *formatblit;
ARGBEGIN { ARGBEGIN {
case 'b':
type[1] = getlinetype(EARGF(usage()), &preg[1]);
break;
case 'd': case 'd':
d = EARGF(usage()); d = EARGF(usage());
l = utflen(d); l = utflen(d);
@ -131,9 +129,9 @@ main(int argc, char *argv[])
break; break;
case 1: case 1:
s = strlen(d); s = strlen(d);
delim = emalloc(s + 2); delim = emalloc(s + 1 + 1);
estrlcpy(delim, d, s + 2); estrlcpy(delim, d, s + 1 + 1);
estrlcat(delim, ":", s + 2); estrlcat(delim, ":", s + 1 + 1);
delimlen = s + 1; delimlen = s + 1;
break; break;
default: default:
@ -143,27 +141,36 @@ main(int argc, char *argv[])
} }
break; break;
case 'f': case 'f':
type[0] = getlinetype(EARGF(usage()), &preg[0]); type[0] = getlinetype(EARGF(usage()), preg);
break;
case 'b':
type[1] = getlinetype(EARGF(usage()), preg + 1);
break; break;
case 'h': case 'h':
type[2] = getlinetype(EARGF(usage()), &preg[2]); type[2] = getlinetype(EARGF(usage()), preg + 2);
break; break;
case 'i': case 'i':
incr = estrtonum(EARGF(usage()), 0, MIN(LLONG_MAX, SIZE_MAX)); incr = estrtonum(EARGF(usage()), 0, MIN(LLONG_MAX, SIZE_MAX));
break; break;
case 'l': case 'l':
blines = estrtonum(EARGF(usage()), 0, UINT_MAX); blines = estrtonum(EARGF(usage()), 0, MIN(LLONG_MAX, SIZE_MAX));
break; break;
case 'n': case 'n':
format = EARGF(usage()); formattype = EARGF(usage());
if (!strcmp(format, "ln")) estrlcpy(format, "%", sizeof(format));
format = FORMAT_LN;
else if (!strcmp(format, "rn")) if (!strcmp(formattype, "ln")) {
format = FORMAT_RN; formatblit = "-";
else if (!strcmp(format, "rz")) } else if (!strcmp(formattype, "rn")) {
format = FORMAT_RZ; formatblit = "";
else } else if (!strcmp(formattype, "rz")) {
eprintf("%s: bad format\n", format); formatblit = "0";
} else {
eprintf("%s: bad format\n", formattype);
}
estrlcat(format, formatblit, sizeof(format));
estrlcat(format, "*ld%s", sizeof(format));
break; break;
case 'p': case 'p':
pflag = 1; pflag = 1;
@ -184,7 +191,7 @@ main(int argc, char *argv[])
if (argc > 1) if (argc > 1)
usage(); usage();
if (argc == 0) { if (!argc) {
nl("<stdin>", stdin); nl("<stdin>", stdin);
} else { } else {
if (!(fp = fopen(argv[0], "r"))) if (!(fp = fopen(argv[0], "r")))
@ -192,5 +199,6 @@ main(int argc, char *argv[])
nl(argv[0], fp); nl(argv[0], fp);
fclose(fp); fclose(fp);
} }
return 0; return 0;
} }