Improve usability of head(1) and tail(1):

- Consistently support -q (quiet) and -v (verbose)
 - Allow specifying numbers with SI prefixes supported by expand_number(3)
 - Remove 2^31 limit on lines for head(1)

MFC after:		2 weeks
Reviewed by:		lwhsu, pauamma, gbe
Relnotes:		yes
Differential Revision: https://reviews.freebsd.org/D35720
This commit is contained in:
Xin LI 2022-07-12 21:14:25 -07:00
parent d8cce8145c
commit 643ac419fa
10 changed files with 173 additions and 30 deletions

View file

@ -4,6 +4,7 @@
.include <src.opts.mk>
PROG= head
LIBADD= util
HAS_TESTS=
SUBDIR.${MK_TESTS}+= tests

View file

@ -28,7 +28,7 @@
.\" @(#)head.1 8.1 (Berkeley) 6/6/93
.\" $FreeBSD$
.\"
.Dd April 10, 2018
.Dd June 12, 2022
.Dt HEAD 1
.Os
.Sh NAME
@ -36,6 +36,7 @@
.Nd display first lines of a file
.Sh SYNOPSIS
.Nm
.Op Fl qv
.Op Fl n Ar count | Fl c Ar bytes
.Op Ar
.Sh DESCRIPTION
@ -59,14 +60,30 @@ of each of the specified files.
Print
.Ar count
lines of each of the specified files.
.Pp
Both
.Ar count
and
.Ar bytes
may also be specified with size suffixes supported by
.Xr expand_number 3 .
.It Fl q , Fl -quiet , Fl -silent
Suppresses printing of headers when multiple files are being examined.
.It Fl v , Fl -verbose
Prepend each file with a header.
.El
.Pp
If more than a single file is specified, each file is preceded by a
If more than a single file is specified, or if the
.Fl v
option is used, each file is preceded by a
header consisting of the string
.Dq ==> XXX <==
where
.Dq XXX
is the name of the file.
The
.Fl q
flag disables the printing of the header in all cases.
.Sh EXIT STATUS
.Ex -std
.Sh EXAMPLES
@ -83,7 +100,8 @@ in the following way to, for example, display only line 500 from the file
.Pp
.Dl $ head -n 500 foo | tail -n 1
.Sh SEE ALSO
.Xr tail 1
.Xr tail 1 ,
.Xr expand_number 3
.Sh HISTORY
The
.Nm

View file

@ -57,6 +57,8 @@ __FBSDID("$FreeBSD$");
#include <string.h>
#include <unistd.h>
#include <libutil.h>
#include <libcasper.h>
#include <casper/cap_fileargs.h>
@ -66,7 +68,7 @@ __FBSDID("$FreeBSD$");
* Bill Joy UCB August 24, 1977
*/
static void head(FILE *, int);
static void head(FILE *, intmax_t);
static void head_bytes(FILE *, off_t);
static void obsolete(char *[]);
static void usage(void);
@ -75,6 +77,9 @@ static const struct option long_opts[] =
{
{"bytes", required_argument, NULL, 'c'},
{"lines", required_argument, NULL, 'n'},
{"quiet", no_argument, NULL, 'q'},
{"silent", no_argument, NULL, 'q'},
{"verbose", no_argument, NULL, 'v'},
{NULL, no_argument, NULL, 0}
};
@ -82,29 +87,37 @@ int
main(int argc, char *argv[])
{
FILE *fp;
char *ep;
off_t bytecnt;
int ch, first, linecnt, eval;
intmax_t linecnt;
int ch, first, eval;
fileargs_t *fa;
cap_rights_t rights;
int qflag = 0;
int vflag = 0;
linecnt = -1;
eval = 0;
bytecnt = -1;
obsolete(argv);
while ((ch = getopt_long(argc, argv, "+n:c:", long_opts, NULL)) != -1) {
while ((ch = getopt_long(argc, argv, "+n:c:qv", long_opts, NULL)) != -1) {
switch(ch) {
case 'c':
bytecnt = strtoimax(optarg, &ep, 10);
if (*ep || bytecnt <= 0)
if (expand_number(optarg, &bytecnt) || bytecnt <= 0)
errx(1, "illegal byte count -- %s", optarg);
break;
case 'n':
linecnt = strtol(optarg, &ep, 10);
if (*ep || linecnt <= 0)
if (expand_number(optarg, &linecnt) || linecnt <= 0)
errx(1, "illegal line count -- %s", optarg);
break;
case 'q':
qflag = 1;
vflag = 0;
break;
case 'v':
qflag = 0;
vflag = 1;
break;
case '?':
default:
usage();
@ -134,7 +147,7 @@ main(int argc, char *argv[])
eval = 1;
continue;
}
if (argc > 1) {
if (vflag || (qflag == 0 && argc > 1)) {
(void)printf("%s==> %s <==\n",
first ? "" : "\n", *argv);
first = 0;
@ -155,7 +168,7 @@ main(int argc, char *argv[])
}
static void
head(FILE *fp, int cnt)
head(FILE *fp, intmax_t cnt)
{
char *cp;
size_t error, readlen;

View file

@ -119,6 +119,48 @@ read_from_stdin_body() {
atf_check cmp outfile expectfile
}
atf_test_case silent_header
silent_header_head() {
atf_set "descr" "Test head(1)'s silent header feature"
}
silent_header_body() {
#head(1) defaults to head -n 10 if no args are given.
jot 11 1 11 > file1
jot 11 2 12 > file2
jot 10 1 10 > expectfile
jot 10 2 11 >> expectfile
head -q file1 file2 > outfile
atf_check cmp outfile expectfile
}
atf_test_case verbose_header
verbose_header_head() {
atf_set "descr" "Test head(1)'s verbose header feature"
}
verbose_header_body() {
#head(1) defaults to head -n 10 if no args are given.
jot -b test 10 > file1
echo '==> file1 <==' > expectfile
cat file1 >> expectfile
head -v file1 > outfile
atf_check cmp outfile expectfile
}
atf_test_case si_number
si_number_head() {
atf_set "descr" "Test head(1)'s SI number feature"
}
si_number_body() {
jot -b aaaaaaa 129 > file1
jot -b aaaaaaa 128 > expectfile
head -c 1k file1 > outfile
atf_check cmp outfile expectfile
jot 1025 1 1025 > file1
jot 1024 1 1024 > expectfile
head -n 1k file1 > outfile
atf_check cmp outfile expectfile
}
atf_init_test_cases() {
atf_add_test_case empty_file
atf_add_test_case default_no_options
@ -129,4 +171,7 @@ atf_init_test_cases() {
atf_add_test_case missing_line_count
atf_add_test_case invalid_line_count
atf_add_test_case read_from_stdin
atf_add_test_case silent_header
atf_add_test_case verbose_header
atf_add_test_case si_number
}

View file

@ -5,6 +5,7 @@
PROG= tail
SRCS= forward.c misc.c read.c reverse.c tail.c
LIBADD= util
.if ${MK_CASPER} != "no" && !defined(RESCUE)
LIBADD+= casper

View file

@ -77,5 +77,5 @@ int mapprint(struct mapinfo *, off_t, off_t);
int maparound(struct mapinfo *, off_t);
void printfn(const char *, int);
extern int Fflag, fflag, qflag, rflag, rval, no_files;
extern int Fflag, fflag, qflag, rflag, rval, no_files, vflag;
extern fileargs_t *fa;

View file

@ -246,8 +246,8 @@ show(file_info_t *file)
int ch;
while ((ch = getc(file->fp)) != EOF) {
if (last != file && no_files > 1) {
if (!qflag)
if (last != file) {
if (vflag || (qflag == 0 && no_files > 1))
printfn(file->file_name, 1);
last = file;
}
@ -325,7 +325,7 @@ follow(file_info_t *files, enum STYLE style, off_t off)
if (file->fp) {
active = 1;
n++;
if (no_files > 1 && !qflag)
if (vflag || (qflag == 0 && no_files > 1))
printfn(file->file_name, 1);
forward(file->fp, file->file_name, style, off, &file->st);
if (Fflag && fileno(file->fp) != STDIN_FILENO)

View file

@ -31,7 +31,7 @@
.\" @(#)tail.1 8.1 (Berkeley) 6/6/93
.\" $FreeBSD$
.\"
.Dd March 22, 2020
.Dd July 12, 2022
.Dt TAIL 1
.Os
.Sh NAME
@ -40,7 +40,7 @@
.Sh SYNOPSIS
.Nm
.Op Fl F | f | r
.Op Fl q
.Op Fl qv
.Oo
.Fl b Ar number | Fl c Ar number | Fl n Ar number
.Oc
@ -116,7 +116,7 @@ option if reading from standard input rather than a file.
The location is
.Ar number
lines.
.It Fl q
.It Fl q, Fl -quiet, Fl -silent
Suppresses printing of headers when multiple files are being examined.
.It Fl r
The
@ -135,16 +135,26 @@ from the beginning or end of the input from which to begin the display.
The default for the
.Fl r
option is to display all of the input.
.It Fl v, Fl -verbose
Prepend each file with a header.
.El
.Pp
If more than a single file is specified, each file is preceded by a
If more than a single file is specified, or if the
.Fl v
option is used, each file is preceded by a
header consisting of the string
.Dq Li "==> " Ns Ar XXX Ns Li " <=="
where
.Ar XXX
is the name of the file unless
is the name of the file.
The
.Fl q
flag is specified.
flag disables the printing of the header in all cases.
.Pp
All
.Ar number
arguments may also be specified with size suffixes supported by
.Xr expand_number 3 .
.Sh EXIT STATUS
.Ex -std
.Sh EXAMPLES
@ -161,7 +171,8 @@ open, displaying to the standard output anything appended to the file:
.Sh SEE ALSO
.Xr cat 1 ,
.Xr head 1 ,
.Xr sed 1
.Xr sed 1 ,
.Xr expand_number 3
.Sh STANDARDS
The
.Nm

View file

@ -59,12 +59,14 @@ static const char sccsid[] = "@(#)tail.c 8.1 (Berkeley) 6/6/93";
#include <string.h>
#include <unistd.h>
#include <libutil.h>
#include <libcasper.h>
#include <casper/cap_fileargs.h>
#include "extern.h"
int Fflag, fflag, qflag, rflag, rval, no_files;
int Fflag, fflag, qflag, rflag, rval, no_files, vflag;
fileargs_t *fa;
static void obsolete(char **);
@ -75,6 +77,9 @@ static const struct option long_opts[] =
{"blocks", required_argument, NULL, 'b'},
{"bytes", required_argument, NULL, 'c'},
{"lines", required_argument, NULL, 'n'},
{"quiet", no_argument, NULL, 'q'},
{"silent", no_argument, NULL, 'q'},
{"verbose", no_argument, NULL, 'v'},
{NULL, no_argument, NULL, 0}
};
@ -88,7 +93,6 @@ main(int argc, char *argv[])
enum STYLE style;
int ch, first;
file_info_t file, *filep, *files;
char *p;
cap_rights_t rights;
/*
@ -106,8 +110,9 @@ main(int argc, char *argv[])
#define ARG(units, forward, backward) { \
if (style) \
usage(); \
off = strtoll(optarg, &p, 10) * (units); \
if (*p) \
if (expand_number(optarg, &off)) \
err(1, "illegal offset -- %s", optarg); \
if (off > INT64_MAX / units || off < INT64_MIN / units ) \
errx(1, "illegal offset -- %s", optarg); \
switch(optarg[0]) { \
case '+': \
@ -127,7 +132,7 @@ main(int argc, char *argv[])
obsolete(argv);
style = NOTSET;
off = 0;
while ((ch = getopt_long(argc, argv, "+Fb:c:fn:qr", long_opts, NULL)) !=
while ((ch = getopt_long(argc, argv, "+Fb:c:fn:qrv", long_opts, NULL)) !=
-1)
switch(ch) {
case 'F': /* -F is superset of (and implies) -f */
@ -147,10 +152,15 @@ main(int argc, char *argv[])
break;
case 'q':
qflag = 1;
vflag = 0;
break;
case 'r':
rflag = 1;
break;
case 'v':
vflag = 1;
qflag = 0;
break;
case '?':
default:
usage();
@ -230,7 +240,7 @@ main(int argc, char *argv[])
ierr(fn);
continue;
}
if (argc > 1 && !qflag) {
if (vflag || (qflag == 0 && argc > 1)) {
printfn(fn, !first);
first = 0;
}

View file

@ -352,6 +352,47 @@ follow_rename_body()
atf_check kill $pid
}
atf_test_case silent_header
silent_header_head() {
atf_set "descr" "Test tail(1)'s silent header feature"
}
silent_header_body() {
jot 11 1 11 > file1
jot 11 2 12 > file2
jot 10 2 11 > expectfile
jot 10 3 12 >> expectfile
tail -q file1 file2 > outfile
atf_check cmp outfile expectfile
}
atf_test_case verbose_header
verbose_header_head() {
atf_set "descr" "Test tail(1)'s verbose header feature"
}
verbose_header_body() {
jot 11 1 11 > file1
echo '==> file1 <==' > expectfile
jot 10 2 11 >> expectfile
tail -v file1 > outfile
atf_check cmp outfile expectfile
}
atf_test_case si_number
si_number_head() {
atf_set "descr" "Test tail(1)'s SI number feature"
}
si_number_body() {
jot -b aaaaaaa 129 > file1
jot -b aaaaaaa 128 > expectfile
tail -c 1k file1 > outfile
atf_check cmp outfile expectfile
jot 1025 1 1025 > file1
jot 1024 2 1025 > expectfile
tail -n 1k file1 > outfile
atf_check cmp outfile expectfile
}
atf_init_test_cases()
{
atf_add_test_case empty_r
@ -372,4 +413,7 @@ atf_init_test_cases()
atf_add_test_case follow
atf_add_test_case follow_stdin
atf_add_test_case follow_rename
atf_add_test_case silent_header
atf_add_test_case verbose_header
atf_add_test_case si_number
}