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

124
nl.c
View File

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