git: 585762c3733f - stable/13 - Improve usability of head(1) and tail(1):
Date: Wed, 13 Dec 2023 20:06:31 UTC
The branch stable/13 has been updated by des: URL: https://cgit.FreeBSD.org/src/commit/?id=585762c3733f3446fe2dad63a1f460903731927c commit 585762c3733f3446fe2dad63a1f460903731927c Author: Xin LI <delphij@FreeBSD.org> AuthorDate: 2022-07-13 04:14:25 +0000 Commit: Dag-Erling Smørgrav <des@FreeBSD.org> CommitDate: 2023-12-13 16:39:38 +0000 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 (cherry picked from commit 643ac419fafba89f5adda0e0ea75b538727453fb) --- usr.bin/head/Makefile | 1 + usr.bin/head/head.1 | 24 +++++++++++++++++++--- usr.bin/head/head.c | 33 +++++++++++++++++++++--------- usr.bin/head/tests/head_test.sh | 45 +++++++++++++++++++++++++++++++++++++++++ usr.bin/tail/Makefile | 1 + usr.bin/tail/extern.h | 2 +- usr.bin/tail/forward.c | 6 +++--- usr.bin/tail/tail.1 | 25 ++++++++++++++++------- usr.bin/tail/tail.c | 22 ++++++++++++++------ usr.bin/tail/tests/tail_test.sh | 44 ++++++++++++++++++++++++++++++++++++++++ 10 files changed, 173 insertions(+), 30 deletions(-) diff --git a/usr.bin/head/Makefile b/usr.bin/head/Makefile index 0b4cf4b2a5d6..dbf9834da9a0 100644 --- a/usr.bin/head/Makefile +++ b/usr.bin/head/Makefile @@ -3,6 +3,7 @@ .include <src.opts.mk> PROG= head +LIBADD= util HAS_TESTS= SUBDIR.${MK_TESTS}+= tests diff --git a/usr.bin/head/head.1 b/usr.bin/head/head.1 index 3a68f5a8ba82..2a0416d9883f 100644 --- a/usr.bin/head/head.1 +++ b/usr.bin/head/head.1 @@ -27,7 +27,7 @@ .\" .\" @(#)head.1 8.1 (Berkeley) 6/6/93 .\" -.Dd April 10, 2018 +.Dd June 12, 2022 .Dt HEAD 1 .Os .Sh NAME @@ -35,6 +35,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 @@ -58,14 +59,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 @@ -82,7 +99,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 diff --git a/usr.bin/head/head.c b/usr.bin/head/head.c index 0b76132e434c..de4faf632979 100644 --- a/usr.bin/head/head.c +++ b/usr.bin/head/head.c @@ -55,6 +55,8 @@ static char sccsid[] = "@(#)head.c 8.2 (Berkeley) 5/4/95"; #include <string.h> #include <unistd.h> +#include <libutil.h> + #include <libcasper.h> #include <casper/cap_fileargs.h> @@ -64,7 +66,7 @@ static char sccsid[] = "@(#)head.c 8.2 (Berkeley) 5/4/95"; * 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); @@ -73,6 +75,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} }; @@ -80,29 +85,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(); @@ -132,7 +145,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; @@ -153,7 +166,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; diff --git a/usr.bin/head/tests/head_test.sh b/usr.bin/head/tests/head_test.sh index fb9f28c1d2af..2dfc1896f6ef 100755 --- a/usr.bin/head/tests/head_test.sh +++ b/usr.bin/head/tests/head_test.sh @@ -118,6 +118,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 @@ -128,4 +170,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 } diff --git a/usr.bin/tail/Makefile b/usr.bin/tail/Makefile index cd85466cdc38..17cf55eb49e5 100644 --- a/usr.bin/tail/Makefile +++ b/usr.bin/tail/Makefile @@ -4,6 +4,7 @@ PROG= tail SRCS= forward.c misc.c read.c reverse.c tail.c +LIBADD= util .if ${MK_CASPER} != "no" && !defined(RESCUE) LIBADD+= casper diff --git a/usr.bin/tail/extern.h b/usr.bin/tail/extern.h index cbf70f27e28d..830f9be79ab1 100644 --- a/usr.bin/tail/extern.h +++ b/usr.bin/tail/extern.h @@ -75,5 +75,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; diff --git a/usr.bin/tail/forward.c b/usr.bin/tail/forward.c index 48516a730a50..f991b2517c48 100644 --- a/usr.bin/tail/forward.c +++ b/usr.bin/tail/forward.c @@ -243,8 +243,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; } @@ -322,7 +322,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) diff --git a/usr.bin/tail/tail.1 b/usr.bin/tail/tail.1 index 19c4046b6683..38e8f95b9f59 100644 --- a/usr.bin/tail/tail.1 +++ b/usr.bin/tail/tail.1 @@ -30,7 +30,7 @@ .\" .\" @(#)tail.1 8.1 (Berkeley) 6/6/93 .\" -.Dd March 22, 2020 +.Dd July 12, 2022 .Dt TAIL 1 .Os .Sh NAME @@ -39,7 +39,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 @@ -115,7 +115,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 @@ -134,16 +134,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 @@ -166,7 +176,8 @@ from the beginning and then follow the file as usual: .Sh SEE ALSO .Xr cat 1 , .Xr head 1 , -.Xr sed 1 +.Xr sed 1 , +.Xr expand_number 3 .Sh STANDARDS The .Nm diff --git a/usr.bin/tail/tail.c b/usr.bin/tail/tail.c index 5babd89135ca..492a6494628d 100644 --- a/usr.bin/tail/tail.c +++ b/usr.bin/tail/tail.c @@ -56,12 +56,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 **); @@ -72,6 +74,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} }; @@ -85,7 +90,6 @@ main(int argc, char *argv[]) enum STYLE style; int ch, first; file_info_t file, *filep, *files; - char *p; cap_rights_t rights; /* @@ -103,8 +107,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 '+': \ @@ -124,7 +129,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 */ @@ -144,10 +149,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(); @@ -227,7 +237,7 @@ main(int argc, char *argv[]) ierr(fn); continue; } - if (argc > 1 && !qflag) { + if (vflag || (qflag == 0 && argc > 1)) { printfn(fn, !first); first = 0; } diff --git a/usr.bin/tail/tests/tail_test.sh b/usr.bin/tail/tests/tail_test.sh index 412d198713e6..0b845dff0cf8 100755 --- a/usr.bin/tail/tests/tail_test.sh +++ b/usr.bin/tail/tests/tail_test.sh @@ -351,6 +351,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 @@ -371,4 +412,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 }