Page MenuHomeFreeBSD

D54227.1775897411.diff
No OneTemporary

Size
10 KB
Referenced Files
None
Subscribers
None

D54227.1775897411.diff

diff --git a/etc/mtree/BSD.tests.dist b/etc/mtree/BSD.tests.dist
--- a/etc/mtree/BSD.tests.dist
+++ b/etc/mtree/BSD.tests.dist
@@ -1223,6 +1223,8 @@
..
stat
..
+ tac
+ ..
tail
..
tar
diff --git a/usr.bin/tac/Makefile b/usr.bin/tac/Makefile
new file mode 100644
--- /dev/null
+++ b/usr.bin/tac/Makefile
@@ -0,0 +1,8 @@
+.include <src.opts.mk>
+
+PROG = tac
+
+HAS_TESTS=
+SUBDIR.${MK_TESTS}+= tests
+
+.include <bsd.prog.mk>
diff --git a/usr.bin/tac/tac.1 b/usr.bin/tac/tac.1
new file mode 100644
--- /dev/null
+++ b/usr.bin/tac/tac.1
@@ -0,0 +1,97 @@
+.\"-
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.\" Copyright (c) 2025 Dag-Erling Smørgrav <des@FreeBSD.org>
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd August 7, 2025
+.Dt TAC 1
+.Os
+.Sh NAME
+.Nm tac
+.Nd print a file in reverse order
+.Sh SYNOPSIS
+.Nm
+.Op Fl bns
+.Op Ar
+.Sh DESCRIPTION
+The
+.Nm
+utility reads text files sequentially and writes them to the standard
+output in reverse order, bottom to top.
+The
+.Ar file
+operands are processed in command-line order.
+If
+.Ar file
+is a single dash
+.Pq Sq \-
+or absent,
+.Nm
+reads from the standard input.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl b
+Number non-blank output lines, starting at 1, in the order in which
+they are printed.
+If both
+.Fl b
+and
+.Fl n
+are specified,
+.Fl b
+takes precedence.
+.It Fl n
+Number all output lines, starting at 1, in the order in which they are
+printed.
+If both
+.Fl b
+and
+.Fl n
+are specified,
+.Fl b
+takes precedence.
+.It Fl s
+Squeeze multiple successive blank lines into one.
+If
+.Fl n
+was specified, omitted lines do not increment the line counter.
+.El
+.Sh EXIT STATUS
+The
+.Nm
+utility exits 0 on success, and >0 if an error occurs.
+.Sh SEE ALSO
+.Xr cat 1
+.Sh HISTORY
+The
+.Nm
+utility was added in
+.Fx 15.0 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+utility and this manual page were written by
+.An Dag-Erling Sm\(/orgrav Aq Mt des@FreeBSD.org .
diff --git a/usr.bin/tac/tac.c b/usr.bin/tac/tac.c
new file mode 100644
--- /dev/null
+++ b/usr.bin/tac/tac.c
@@ -0,0 +1,181 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2025 Dag-Erling Smørgrav <des@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/stat.h>
+
+#include <err.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+static bool bflag, nflag, sflag;
+
+static void
+ftac(FILE *f, const char *fn)
+{
+ struct stat sb;
+ char *buf, *end, *line, *p;
+ size_t *lines;
+ size_t len, szbuf, szlines, nlines;
+ unsigned long n;
+ bool blank;
+
+ /* get file size if possible, otherwise we will realloc */
+ if (fstat(fileno(f), &sb) == 0)
+ szbuf = sb.st_size;
+ else
+ szbuf = BUFSIZ;
+ szlines = 64;
+
+ /* allocate buf for text and lines for line offsets */
+ if ((end = buf = malloc(szbuf)) == NULL ||
+ (lines = malloc(szlines * sizeof(*lines))) == NULL)
+ err(EXIT_FAILURE, NULL);
+
+ /* first line */
+ lines[1] = 0;
+ nlines = 1;
+
+ /* read the file */
+ for (;;) {
+ /* grow the buffer if full */
+ if (end == buf + szbuf) {
+ szbuf *= 2;
+ if ((buf = realloc(buf, szbuf)) == NULL)
+ err(EXIT_FAILURE, NULL);
+ end = buf + szbuf / 2;
+ }
+ /* read more data into the buffer */
+ if ((len = fread(end, 1, szbuf - (end - buf), f)) == 0) {
+ if (ferror(f))
+ err(EXIT_FAILURE, "%s", fn);
+ break;
+ }
+ /* locate newlines */
+ for (p = end; p < end + len; p++) {
+ if (*p != '\n')
+ continue;
+ /* grow the line offset array if full */
+ if (nlines == szlines) {
+ szlines *= 2;
+ if ((lines = reallocarray(lines, szlines,
+ sizeof(*lines))) == NULL)
+ err(EXIT_FAILURE, NULL);
+ }
+ lines[nlines++] = p - buf + 1;
+ }
+ end += len;
+ }
+
+ /* dummy last line? */
+ if (buf + lines[nlines - 1] == end)
+ nlines--;
+
+ /* non-empty file? */
+ if (end > buf) {
+ /* print in reverse order */
+ flockfile(stdout);
+ for (n = 1; nlines; end = line) {
+ line = buf + lines[--nlines];
+ /* squeeze consecutive blank lines */
+ if (*line == '\n' && blank && sflag)
+ continue;
+ blank = *line == '\n';
+ /* print line number if requested */
+ if (nflag && (!blank || !bflag))
+ (void)fprintf(stdout, "%6lu\t", n++);
+ /* print the line */
+ (void)fwrite_unlocked(line, 1, end - line, stdout);
+ /* print a newline if there wasn't one already */
+ if (line[end - line - 1] != '\n')
+ (void)fputc_unlocked('\n', stdout);
+ /* check for errors */
+ if (ferror(stdout))
+ err(EXIT_FAILURE, "stdout");
+ }
+ funlockfile(stdout);
+ }
+ free(lines);
+ free(buf);
+}
+
+static void
+tac(const char *fn)
+{
+ FILE *f;
+
+ if (fn[0] == '-' && fn[1] == '\0') {
+ f = stdin;
+ fn = "stdin";
+ } else {
+ if ((f = fopen(fn, "r")) == NULL)
+ err(EXIT_FAILURE, "%s", fn);
+ }
+ ftac(f, fn);
+ if (f != stdin)
+ fclose(f);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: tac [-bns] [file [...]]\n");
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int opt;
+
+ while ((opt = getopt(argc, argv, "bns")) != -1) {
+ switch (opt) {
+ case 'b':
+ bflag = nflag = true;
+ break;
+ case 'n':
+ nflag = true;
+ break;
+ case 's':
+ sflag = true;
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0) {
+ tac("-");
+ } else {
+ while (argc--)
+ tac(*argv++);
+ }
+ exit(EXIT_SUCCESS);
+}
diff --git a/usr.bin/tac/tests/Makefile b/usr.bin/tac/tests/Makefile
new file mode 100644
--- /dev/null
+++ b/usr.bin/tac/tests/Makefile
@@ -0,0 +1,4 @@
+PACKAGE= tests
+ATF_TESTS_SH= tac_test
+
+.include <bsd.test.mk>
diff --git a/usr.bin/tac/tests/tac_test.sh b/usr.bin/tac/tests/tac_test.sh
new file mode 100644
--- /dev/null
+++ b/usr.bin/tac/tests/tac_test.sh
@@ -0,0 +1,97 @@
+#
+# Copyright (c) 2025 Dag-Erling Smørgrav <des@FreeBSD.org>
+#
+# SPDX-License-Identifier: BSD-2-Clause
+#
+
+fwd="The
+magic
+words
+are
+x
+Squeamish
+Ossifrage"
+rev="Ossifrage
+Squeamish
+x
+are
+words
+magic
+The"
+
+atf_test_case basic
+basic_head() {
+ atf_set descr "Basic operation"
+}
+basic_body() {
+ echo "$fwd" >input
+ echo "$rev" >expect
+ atf_check -o file:expect tac input
+ atf_check -o file:expect tac <input
+ atf_check -o file:expect tac - <input
+ atf_check -o file:expect tac - - <input
+}
+
+atf_test_case empty
+empty_head() {
+ atf_set descr "Empty input"
+}
+empty_body() {
+ :>empty
+ atf_check tac empty
+ atf_check tac <empty
+ atf_check tac - <empty
+ atf_check tac empty empty empty
+}
+
+atf_test_case count
+count_head() {
+ atf_set descr "Line counting"
+}
+count_body() {
+ echo "$fwd" | sed 's/x/\n/' >input
+ echo "$rev" | sed 's/x/\n/' >output
+ cat -n output >expect
+ atf_check -o file:expect tac -n input
+ cat -b output >expect
+ atf_check -o file:expect tac -b input
+ atf_check -o file:expect tac -bn input
+ atf_check -o file:expect tac -nb input
+}
+
+atf_test_case squeeze
+squeeze_head() {
+ atf_set descr "Line squeezeing"
+}
+squeeze_body() {
+ echo "$fwd" | sed 's/x/\n\n/' >input
+ echo "$rev" | sed 's/x/\n\n/' >output
+ atf_check -o file:output tac input
+ cat -s output >expect
+ atf_check -o file:expect tac -s input
+ cat -sn output >expect
+ atf_check -o file:expect tac -sn input
+ cat -snb output >expect
+ atf_check -o file:expect tac -snb input
+}
+
+atf_test_case multi
+multi_head() {
+ atf_set descr "Multiple inputs"
+}
+multi_body() {
+ printf "a\nx\n" >a
+ printf "b\ny\n" >b
+ printf "c\nz\n" >c
+ printf "x\na\ny\nb\nz\nc\n" >expect
+ atf_check -o file:expect tac - b c <a
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case basic
+ atf_add_test_case empty
+ atf_add_test_case count
+ atf_add_test_case squeeze
+ atf_add_test_case multi
+}

File Metadata

Mime Type
text/plain
Expires
Sat, Apr 11, 8:50 AM (13 h, 42 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
28323835
Default Alt Text
D54227.1775897411.diff (10 KB)

Event Timeline