Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F144632015
D54227.1775897411.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
10 KB
Referenced Files
None
Subscribers
None
D54227.1775897411.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D54227: Add a tac utility
Attached
Detach File
Event Timeline
Log In to Comment