about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Makefile23
-rw-r--r--rnl.153
-rw-r--r--rnl.c113
3 files changed, 189 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..ec1a17c
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,23 @@
+ALL=rnl
+
+CFLAGS=-g -O2 -Wall -Wno-switch -Wextra -Wwrite-strings
+
+DESTDIR=
+PREFIX=/usr/local
+BINDIR=$(PREFIX)/bin
+MANDIR=$(PREFIX)/share/man
+
+all: $(ALL)
+
+README: rnl.1
+	mandoc -Tutf8 $< | col -bx >$@
+
+clean: FRC
+	rm -f $(ALL)
+
+install: FRC all
+	mkdir -p $(DESTDIR)$(BINDIR) $(DESTDIR)$(MANDIR)/man1
+	install -m0755 $(ALL) $(DESTDIR)$(BINDIR)
+	install -m0644 $(ALL:=.1) $(DESTDIR)$(MANDIR)/man1
+
+FRC:
diff --git a/rnl.1 b/rnl.1
new file mode 100644
index 0000000..38c6769
--- /dev/null
+++ b/rnl.1
@@ -0,0 +1,53 @@
+.Dd December 13, 2017
+.Dt RNL 1
+.Os
+.Sh NAME
+.Nm rnl
+.Nd remove trailing newlines
+.Sh SYNOPSIS
+.Nm
+.Op Fl 01sz
+.Op Ar files\ ...
+.Sh DESCRIPTION
+.Nm
+removes trailing newlines from the specified input
+.Ar files .
+The files are modified in-place!
+.Pp
+When used without arguments,
+.Nm
+copies standard input to standard output,
+removing trailing newlines.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl 0
+Remove
+.Em all
+trailing newlines.
+.It Fl 1
+Remove just
+.Em one
+trailing newline.
+.It Fl s
+Strip all but one newline at the end.
+(This is the default.)
+.It Fl z
+Remove final NUL bytes instead of newlines.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr tr 1
+.Sh AUTHORS
+.An Leah Neukirchen Aq Mt leah@vuxu.org
+.Sh LICENSE
+.Nm
+is in the public domain.
+.Pp
+To the extent possible under law,
+the creator of this work
+has waived all copyright and related or
+neighboring rights to this work.
+.Pp
+.Lk http://creativecommons.org/publicdomain/zero/1.0/
diff --git a/rnl.c b/rnl.c
new file mode 100644
index 0000000..b1bac2a
--- /dev/null
+++ b/rnl.c
@@ -0,0 +1,113 @@
+/* rnl - remove final newlines */
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+int
+main(int argc, char *argv[])
+{
+	int c;
+	int i, n = 0;
+	int mode = -1;
+	char nl = '\n';
+
+	while ((c = getopt(argc, argv, "01sz")) != -1)
+		switch (c) {
+		case '0': mode = 0; break;
+		case '1': mode = 1; break;
+		case 's': mode = -1; break;
+		case 'z': nl = '\0'; break;
+		default:
+			fprintf(stderr,
+"Usage: %s [-01sz] < INPUT           writes to stdout\n"
+"       %s [-01sz] INPUT...          modifies in-place!\n"
+"  -0  strip all newlines at end\n"
+"  -1  strip one newline at end\n"
+"  -s  default: strip all but one newline at end\n"
+"  -z  strip NUL bytes instead of newlines\n",
+			    argv[0], argv[0]);
+			exit(1);
+		}
+
+	if (optind == argc) {
+		/* pipe mode stdin -> stdout */
+
+		while ((c = getchar()) != EOF) {
+			if (c == nl) {
+				n++;
+				continue;
+			}
+			for (i = 0; i < n; i++)
+				putchar(nl);
+			n = 0;
+			putchar(c);
+		}
+		if (mode == 1)
+			for (i = 0; i < n - 1; i++)
+				putchar(nl);
+		else if (mode == -1 && n > 0)
+			putchar(nl);
+		/* else (mode == 0) ; do nothing */
+
+		return 0;
+	}
+
+	/* in-place mode */
+	for (i = optind; i < argc; i++) {
+		int fd = open(argv[i], O_RDWR);
+		if (fd < 0)
+			perror("rnl: open");
+
+		off_t pos = lseek(fd, 0, SEEK_END);
+
+		if (mode == 1) {
+			if (pos > 0) {
+				char buf;
+				if (pread(fd, &buf, 1, pos - 1) != 1)
+					perror("rnl: pread");
+				if (buf == nl)
+					if (ftruncate(fd, pos - 1) < 0)
+						perror("rnl: ftruncate");
+
+			}
+		} else {
+			char buf[1024];
+			off_t n = 0;
+
+			off_t endpos = pos;
+			ssize_t rd;
+			do {
+				off_t l = sizeof buf;
+				if (pos > l)
+					pos -= l;
+				else {
+					l = pos;
+					pos = 0;
+				}
+				rd = pread(fd, buf, l, pos);
+				while (rd > 0 && buf[--rd] == nl)
+					n++;
+			} while (rd == 0 && n > 0 && pos > 0);
+
+			if (rd < 0)
+				perror("rnl: pread");
+
+			if (mode == -1 && n > 0)
+				n--;
+
+			if (n > 0)
+				if (ftruncate(fd, endpos - n) < 0)
+					perror("rnl: ftruncate");
+		}
+
+		close(fd);
+	}
+
+	return 0;
+}