aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXin LI <delphij@FreeBSD.org>2022-07-13 04:14:25 +0000
committerXin LI <delphij@FreeBSD.org>2022-07-13 04:14:25 +0000
commit643ac419fafba89f5adda0e0ea75b538727453fb (patch)
tree2ce7e4cd870fecdad6036f3804b89d3ffbb6c441
parentd8cce8145c39812cc31b50070c44d66ca21a5127 (diff)
downloadsrc-643ac419fafb.tar.gz
src-643ac419fafb.zip
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
-rw-r--r--usr.bin/head/Makefile1
-rw-r--r--usr.bin/head/head.124
-rw-r--r--usr.bin/head/head.c33
-rwxr-xr-xusr.bin/head/tests/head_test.sh45
-rw-r--r--usr.bin/tail/Makefile1
-rw-r--r--usr.bin/tail/extern.h2
-rw-r--r--usr.bin/tail/forward.c6
-rw-r--r--usr.bin/tail/tail.125
-rw-r--r--usr.bin/tail/tail.c22
-rwxr-xr-xusr.bin/tail/tests/tail_test.sh44
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
}