git: 643ac419fafb - main - Improve usability of head(1) and tail(1):
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Wed, 13 Jul 2022 04:14:38 UTC
The branch main has been updated by delphij: URL: https://cgit.FreeBSD.org/src/commit/?id=643ac419fafba89f5adda0e0ea75b538727453fb commit 643ac419fafba89f5adda0e0ea75b538727453fb Author: Xin LI <delphij@FreeBSD.org> AuthorDate: 2022-07-13 04:14:25 +0000 Commit: Xin LI <delphij@FreeBSD.org> CommitDate: 2022-07-13 04:14:25 +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 --- 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 b966f8165795..0c9056924969 100644 --- a/usr.bin/head/Makefile +++ b/usr.bin/head/Makefile @@ -4,6 +4,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 c74983822916..3968e7283b17 100644 --- a/usr.bin/head/head.1 +++ b/usr.bin/head/head.1 @@ -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 diff --git a/usr.bin/head/head.c b/usr.bin/head/head.c index 0226dd96d9a6..1c6368823e7c 100644 --- a/usr.bin/head/head.c +++ b/usr.bin/head/head.c @@ -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; diff --git a/usr.bin/head/tests/head_test.sh b/usr.bin/head/tests/head_test.sh index 1fc3cb10b455..9d1b97507c12 100755 --- a/usr.bin/head/tests/head_test.sh +++ b/usr.bin/head/tests/head_test.sh @@ -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 } diff --git a/usr.bin/tail/Makefile b/usr.bin/tail/Makefile index 5d53b23957a7..b53d9d2c215f 100644 --- a/usr.bin/tail/Makefile +++ b/usr.bin/tail/Makefile @@ -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 diff --git a/usr.bin/tail/extern.h b/usr.bin/tail/extern.h index 3d8c12629682..d78aa37dc2ff 100644 --- a/usr.bin/tail/extern.h +++ b/usr.bin/tail/extern.h @@ -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; diff --git a/usr.bin/tail/forward.c b/usr.bin/tail/forward.c index 878cb5a4550b..3897d115effd 100644 --- a/usr.bin/tail/forward.c +++ b/usr.bin/tail/forward.c @@ -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) diff --git a/usr.bin/tail/tail.1 b/usr.bin/tail/tail.1 index 771d72c56989..59b632868dbd 100644 --- a/usr.bin/tail/tail.1 +++ b/usr.bin/tail/tail.1 @@ -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 diff --git a/usr.bin/tail/tail.c b/usr.bin/tail/tail.c index 874557f105ec..51598d451bed 100644 --- a/usr.bin/tail/tail.c +++ b/usr.bin/tail/tail.c @@ -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; } diff --git a/usr.bin/tail/tests/tail_test.sh b/usr.bin/tail/tests/tail_test.sh index 66d435f2cd02..e8263864f28c 100755 --- a/usr.bin/tail/tests/tail_test.sh +++ b/usr.bin/tail/tests/tail_test.sh @@ -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 }