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:
parent
d8cce8145c
commit
643ac419fa
@ -4,6 +4,7 @@
|
||||
.include <src.opts.mk>
|
||||
|
||||
PROG= head
|
||||
LIBADD= util
|
||||
|
||||
HAS_TESTS=
|
||||
SUBDIR.${MK_TESTS}+= tests
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user