aboutsummaryrefslogtreecommitdiff
path: root/usr.bin/rs
diff options
context:
space:
mode:
Diffstat (limited to 'usr.bin/rs')
-rw-r--r--usr.bin/rs/Makefile8
-rw-r--r--usr.bin/rs/Makefile.depend18
-rw-r--r--usr.bin/rs/rs.1244
-rw-r--r--usr.bin/rs/rs.cc462
-rw-r--r--usr.bin/rs/tests/Makefile3
-rw-r--r--usr.bin/rs/tests/rs_test.sh396
6 files changed, 1131 insertions, 0 deletions
diff --git a/usr.bin/rs/Makefile b/usr.bin/rs/Makefile
new file mode 100644
index 000000000000..ed98f6826ce4
--- /dev/null
+++ b/usr.bin/rs/Makefile
@@ -0,0 +1,8 @@
+.include <src.opts.mk>
+
+PROG_CXX= rs
+
+HAS_TESTS=
+SUBDIR.${MK_TESTS}+= tests
+
+.include <bsd.prog.mk>
diff --git a/usr.bin/rs/Makefile.depend b/usr.bin/rs/Makefile.depend
new file mode 100644
index 000000000000..76a8d2cdc8ca
--- /dev/null
+++ b/usr.bin/rs/Makefile.depend
@@ -0,0 +1,18 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libc++ \
+ lib/libcompiler_rt \
+ lib/libcxxrt \
+ lib/msun \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/usr.bin/rs/rs.1 b/usr.bin/rs/rs.1
new file mode 100644
index 000000000000..dd0c12c33950
--- /dev/null
+++ b/usr.bin/rs/rs.1
@@ -0,0 +1,244 @@
+.\" Copyright (c) 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" 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.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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 April 7, 2015
+.Dt RS 1
+.Os
+.Sh NAME
+.Nm rs
+.Nd reshape a data array
+.Sh SYNOPSIS
+.Nm
+.Oo
+.Fl Oo Cm csCS Oc Ns Op Ar x
+.Oo Cm kKgGw Oc Ns Op Ar N
+.Cm tTeEnyjhHmz
+.Oc
+.Op Ar rows Op Ar cols
+.Sh DESCRIPTION
+The
+.Nm
+utility reads the standard input, interpreting each line as a row
+of blank-separated entries in an array,
+transforms the array according to the options,
+and writes it on the standard output.
+With no arguments it transforms stream input into a columnar
+format convenient for terminal viewing.
+.Pp
+The shape of the input array is deduced from the number of lines
+and the number of columns on the first line.
+If that shape is inconvenient, a more useful one might be
+obtained by skipping some of the input with the
+.Fl k
+option.
+Other options control interpretation of the input columns.
+.Pp
+The shape of the output array is influenced by the
+.Ar rows
+and
+.Ar cols
+specifications, which should be positive integers.
+If only one of them is a positive integer,
+.Nm
+computes a value for the other which will accommodate
+all of the data.
+When necessary, missing data are supplied in a manner
+specified by the options and surplus data are deleted.
+There are options to control presentation of the output columns,
+including transposition of the rows and columns.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl c Ns Ar x
+Input columns are delimited by the single character
+.Ar x .
+A missing
+.Ar x
+is taken to be `^I'.
+.It Fl s Ns Ar x
+Like
+.Fl c ,
+but maximal strings of
+.Ar x
+are delimiters.
+.It Fl C Ns Ar x
+Output columns are delimited by the single character
+.Ar x .
+A missing
+.Ar x
+is taken to be `^I'.
+.It Fl S Ns Ar x
+Like
+.Fl C ,
+but padded strings of
+.Ar x
+are delimiters.
+.It Fl t
+Fill in the rows of the output array using the columns of the
+input array, that is, transpose the input while honoring any
+.Ar rows
+and
+.Ar cols
+specifications.
+.It Fl T
+Print the pure transpose of the input, ignoring any
+.Ar rows
+or
+.Ar cols
+specification.
+.It Fl k Ns Ar N
+Ignore the first
+.Ar N
+lines of input.
+.It Fl K Ns Ar N
+Like
+.Fl k ,
+but print the ignored lines.
+.It Fl g Ns Ar N
+The gutter width (inter-column space), normally 2, is taken to be
+.Ar N .
+.It Fl G Ns Ar N
+The gutter width has
+.Ar N
+percent of the maximum column width added to it.
+.It Fl e
+Consider each line of input as an array entry.
+.It Fl n
+On lines having fewer entries than the first line,
+use null entries to pad out the line.
+Normally, missing entries are taken from the next line of input.
+.It Fl y
+If there are too few entries to make up the output dimensions,
+pad the output by recycling the input from the beginning.
+Normally, the output is padded with blanks.
+.It Fl h
+Print the shape of the input array and do nothing else.
+The shape is just the number of lines and the number of
+entries on the first line.
+.It Fl H
+Like
+.Fl h ,
+but also print the length of each line.
+.It Fl j
+Right adjust entries within columns.
+.It Fl w Ns Ar N
+The width of the display, normally 80, is taken to be the positive
+integer
+.Ar N .
+.It Fl m
+Do not trim excess delimiters from the ends of the output array.
+.It Fl z
+Adapt column widths to fit the largest entries appearing in them.
+.El
+.Pp
+With no arguments,
+.Nm
+transposes its input, and assumes one array entry per input line
+unless the first non-ignored line is longer than the display width.
+Option letters which take numerical arguments interpret a missing
+number as zero unless otherwise indicated.
+.Sh EXAMPLES
+The
+.Nm
+utility can be used as a filter to convert the stream output
+of certain programs (e.g.,
+.Xr spell 1 ,
+.Xr du 1 ,
+.Xr file 1 ,
+.Xr look 1 ,
+.Xr nm 1 ,
+.Xr who 1 ,
+and
+.Xr wc 1 )
+into a convenient ``window'' format, as in
+.Bd -literal -offset indent
+% who | rs
+.Ed
+.Pp
+This function has been incorporated into the
+.Xr ls 1
+program, though for most programs with similar output
+.Nm
+suffices.
+.Pp
+To convert stream input into vector output and back again, use
+.Bd -literal -offset indent
+% rs 1 0 | rs 0 1
+.Ed
+.Pp
+A 10 by 10 array of random numbers from 1 to 100 and
+its transpose can be generated with
+.Bd -literal -offset indent
+% jot \-r 100 | rs 10 10 | tee array | rs \-T > tarray
+.Ed
+.Pp
+In the editor
+.Xr vi 1 ,
+a file consisting of a multi-line vector with 9 elements per line
+can undergo insertions and deletions,
+and then be neatly reshaped into 9 columns with
+.Bd -literal -offset indent
+:1,$!rs 0 9
+.Ed
+.Pp
+Finally, to sort a database by the first line of each 4-line field, try
+.Bd -literal -offset indent
+% rs \-eC 0 4 | sort | rs \-c 0 1
+.Ed
+.Sh SEE ALSO
+.Xr jot 1 ,
+.Xr pr 1 ,
+.Xr sort 1 ,
+.Xr vi 1
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Bx 4.2 .
+.Sh AUTHORS
+.An John A. Kunze
+.Sh BUGS
+.Bl -item
+.It
+Handles only two dimensional arrays.
+.It
+The algorithm currently reads the whole file into memory,
+so files that do not fit in memory will not be reshaped.
+.It
+Fields cannot be defined yet on character positions.
+.It
+Re-ordering of columns is not yet possible.
+.It
+There are too many options.
+.It
+Multibyte characters are not recognized.
+.It
+Lines longer than
+.Dv LINE_MAX
+(2048) bytes are not processed and result in immediate termination of
+.Nm .
+.El
diff --git a/usr.bin/rs/rs.cc b/usr.bin/rs/rs.cc
new file mode 100644
index 000000000000..8989288e3ec7
--- /dev/null
+++ b/usr.bin/rs/rs.cc
@@ -0,0 +1,462 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * 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.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
+ */
+
+/*
+ * rs - reshape a data array
+ * Author: John Kunze, Office of Comp. Affairs, UCB
+ * BEWARE: lots of unfinished edges
+ */
+
+#include <err.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <vector>
+
+static long flags;
+#define TRANSPOSE 000001
+#define MTRANSPOSE 000002
+#define ONEPERLINE 000004
+#define ONEISEPONLY 000010
+#define ONEOSEPONLY 000020
+#define NOTRIMENDCOL 000040
+#define SQUEEZE 000100
+#define SHAPEONLY 000200
+#define DETAILSHAPE 000400
+#define RIGHTADJUST 001000
+#define NULLPAD 002000
+#define RECYCLE 004000
+#define SKIPPRINT 010000
+#define ICOLBOUNDS 020000
+#define OCOLBOUNDS 040000
+#define ONEPERCHAR 0100000
+#define NOARGS 0200000
+
+static short *colwidths;
+static std::vector<char *> elem;
+static char *curline;
+static size_t curlen;
+static size_t irows, icols;
+static size_t orows = 0, ocols = 0;
+static size_t maxlen;
+static int skip;
+static int propgutter;
+static char isep = ' ', osep = ' ';
+static char blank[] = "";
+static size_t owidth = 80, gutter = 2;
+
+static void getargs(int, char *[]);
+static void getfile(void);
+static int get_line(void);
+static long getnum(const char *);
+static void prepfile(void);
+static void prints(char *, int);
+static void putfile(void);
+static void usage(void);
+
+int
+main(int argc, char *argv[])
+{
+ getargs(argc, argv);
+ getfile();
+ if (flags & SHAPEONLY) {
+ printf("%zu %zu\n", irows, icols);
+ exit(0);
+ }
+ prepfile();
+ putfile();
+ exit(0);
+}
+
+static void
+getfile(void)
+{
+ char *p, *sp;
+ char *endp;
+ int c;
+ int multisep = (flags & ONEISEPONLY ? 0 : 1);
+ int nullpad = flags & NULLPAD;
+ size_t len, padto;
+
+ while (skip--) {
+ c = get_line();
+ if (flags & SKIPPRINT)
+ puts(curline);
+ if (c == EOF)
+ return;
+ }
+ get_line();
+ if (flags & NOARGS && curlen < owidth)
+ flags |= ONEPERLINE;
+ if (flags & ONEPERLINE)
+ icols = 1;
+ else /* count cols on first line */
+ for (p = curline, endp = curline + curlen; p < endp; p++) {
+ if (*p == isep && multisep)
+ continue;
+ icols++;
+ while (*p && *p != isep)
+ p++;
+ }
+ do {
+ if (flags & ONEPERLINE) {
+ elem.push_back(curline);
+ if (maxlen < curlen)
+ maxlen = curlen;
+ irows++;
+ continue;
+ }
+ for (p = curline, endp = curline + curlen; p < endp; p++) {
+ if (*p == isep && multisep)
+ continue; /* eat up column separators */
+ if (*p == isep) /* must be an empty column */
+ elem.push_back(blank);
+ else /* store column entry */
+ elem.push_back(p);
+ sp = p;
+ while (p < endp && *p != isep)
+ p++; /* find end of entry */
+ *p = '\0'; /* mark end of entry */
+ len = p - sp;
+ if (maxlen < len) /* update maxlen */
+ maxlen = len;
+ }
+ irows++; /* update row count */
+ if (nullpad) { /* pad missing entries */
+ padto = irows * icols;
+ elem.resize(padto, blank);
+ }
+ } while (get_line() != EOF);
+}
+
+static void
+putfile(void)
+{
+ size_t i, j, k;
+
+ if (flags & TRANSPOSE)
+ for (i = 0; i < orows; i++) {
+ for (j = i; j < elem.size(); j += orows)
+ prints(elem[j], (j - i) / orows);
+ putchar('\n');
+ }
+ else
+ for (i = k = 0; i < orows; i++) {
+ for (j = 0; j < ocols; j++, k++)
+ if (k < elem.size())
+ prints(elem[k], j);
+ putchar('\n');
+ }
+}
+
+static void
+prints(char *s, int col)
+{
+ int n;
+ char *p = s;
+
+ while (*p)
+ p++;
+ n = (flags & ONEOSEPONLY ? 1 : colwidths[col] - (p - s));
+ if (flags & RIGHTADJUST)
+ while (n-- > 0)
+ putchar(osep);
+ for (p = s; *p; p++)
+ putchar(*p);
+ while (n-- > 0)
+ putchar(osep);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr,
+ "usage: rs [-[csCS][x][kKgGw][N]tTeEnyjhHmz] [rows [cols]]\n");
+ exit(1);
+}
+
+static void
+prepfile(void)
+{
+ size_t i, j;
+ size_t colw, max, n, orig_size, padto;
+
+ if (elem.empty())
+ exit(0);
+ gutter += maxlen * propgutter / 100.0;
+ colw = maxlen + gutter;
+ if (flags & MTRANSPOSE) {
+ orows = icols;
+ ocols = irows;
+ }
+ else if (orows == 0 && ocols == 0) { /* decide rows and cols */
+ ocols = owidth / colw;
+ if (ocols == 0) {
+ warnx("display width %zu is less than column width %zu",
+ owidth, colw);
+ ocols = 1;
+ }
+ if (ocols > elem.size())
+ ocols = elem.size();
+ orows = elem.size() / ocols + (elem.size() % ocols ? 1 : 0);
+ }
+ else if (orows == 0) /* decide on rows */
+ orows = elem.size() / ocols + (elem.size() % ocols ? 1 : 0);
+ else if (ocols == 0) /* decide on cols */
+ ocols = elem.size() / orows + (elem.size() % orows ? 1 : 0);
+ padto = orows * ocols;
+ orig_size = elem.size();
+ if (flags & RECYCLE) {
+ for (i = 0; elem.size() < padto; i++)
+ elem.push_back(elem[i % orig_size]);
+ }
+ if (!(colwidths = (short *) malloc(ocols * sizeof(short))))
+ errx(1, "malloc");
+ if (flags & SQUEEZE) {
+ if (flags & TRANSPOSE) {
+ auto it = elem.begin();
+ for (i = 0; i < ocols; i++) {
+ max = 0;
+ for (j = 0; it != elem.end() && j < orows; j++)
+ if ((n = strlen(*it++)) > max)
+ max = n;
+ colwidths[i] = max + gutter;
+ }
+ } else {
+ for (i = 0; i < ocols; i++) {
+ max = 0;
+ for (j = i; j < elem.size(); j += ocols)
+ if ((n = strlen(elem[j])) > max)
+ max = n;
+ colwidths[i] = max + gutter;
+ }
+ }
+ }
+ /* for (i = 0; i < orows; i++) {
+ for (j = i; j < elem.size(); j += orows)
+ prints(elem[j], (j - i) / orows);
+ putchar('\n');
+ }
+ else {
+ auto it = elem.begin();
+ for (i = 0; i < orows; i++) {
+ for (j = 0; j < ocols; j++)
+ prints(*it++, j);
+ putchar('\n');
+ }*/
+ else
+ for (i = 0; i < ocols; i++)
+ colwidths[i] = colw;
+ if (!(flags & NOTRIMENDCOL)) {
+ if (flags & RIGHTADJUST)
+ colwidths[0] -= gutter;
+ else
+ colwidths[ocols - 1] = 0;
+ }
+ /*for (i = 0; i < ocols; i++)
+ warnx("%d is colwidths, nelem %zu", colwidths[i], elem.size());*/
+}
+
+#define BSIZE (LINE_MAX * 2)
+static char ibuf[BSIZE];
+
+static int
+get_line(void) /* get line; maintain curline, curlen; manage storage */
+{
+ static int putlength;
+ static char *endblock = ibuf + BSIZE;
+ char *p;
+ int c, i;
+
+ if (irows == 0) {
+ curline = ibuf;
+ putlength = flags & DETAILSHAPE;
+ }
+ else if (skip <= 0) { /* don't waste storage */
+ curline += curlen + 1;
+ if (putlength) { /* print length, recycle storage */
+ printf(" %zu line %zu\n", curlen, irows);
+ curline = ibuf;
+ }
+ }
+ if (!putlength && endblock - curline < LINE_MAX + 1) { /* need storage */
+ /*ww = endblock-curline; tt += ww;*/
+ /*printf("#wasted %d total %d\n",ww,tt);*/
+ if (!(curline = (char *) malloc(BSIZE)))
+ errx(1, "file too large");
+ endblock = curline + BSIZE;
+ /*printf("#endb %d curline %d\n",endblock,curline);*/
+ }
+ for (p = curline, i = 0;; *p++ = c, i++) {
+ if ((c = getchar()) == EOF)
+ break;
+ if (i >= LINE_MAX)
+ errx(1, "maximum line length (%d) exceeded", LINE_MAX);
+ if (c == '\n')
+ break;
+ }
+ *p = '\0';
+ curlen = i;
+ return(c);
+}
+
+static void
+getargs(int ac, char *av[])
+{
+ long val;
+ int ch;
+
+ if (ac == 1) {
+ flags |= NOARGS | TRANSPOSE;
+ }
+
+ while ((ch = getopt(ac, av, "C::EG:HK:S::Tc::eg:hjk:mns::tw:yz")) != -1)
+ switch (ch) {
+ case 'T':
+ flags |= MTRANSPOSE;
+ /* FALLTHROUGH */
+ case 't':
+ flags |= TRANSPOSE;
+ break;
+ case 'c': /* input col. separator */
+ flags |= ONEISEPONLY;
+ /* FALLTHROUGH */
+ case 's': /* one or more allowed */
+ if (optarg != NULL)
+ isep = *optarg;
+ else
+ isep = '\t'; /* default is ^I */
+ break;
+ case 'C':
+ flags |= ONEOSEPONLY;
+ /* FALLTHROUGH */
+ case 'S':
+ if (optarg != NULL)
+ osep = *optarg;
+ else
+ osep = '\t'; /* default is ^I */
+ break;
+ case 'w': /* window width, default 80 */
+ val = getnum(optarg);
+ if (val <= 0)
+ errx(1, "width must be a positive integer");
+ owidth = val;
+ break;
+ case 'K': /* skip N lines */
+ flags |= SKIPPRINT;
+ /* FALLTHROUGH */
+ case 'k': /* skip, do not print */
+ skip = getnum(optarg);
+ if (skip < 1)
+ skip = 1;
+ break;
+ case 'm':
+ flags |= NOTRIMENDCOL;
+ break;
+ case 'g': /* gutter space */
+ gutter = getnum(optarg);
+ break;
+ case 'G':
+ propgutter = getnum(optarg);
+ break;
+ case 'e': /* each line is an entry */
+ flags |= ONEPERLINE;
+ break;
+ case 'E':
+ flags |= ONEPERCHAR;
+ break;
+ case 'j': /* right adjust */
+ flags |= RIGHTADJUST;
+ break;
+ case 'n': /* null padding for missing values */
+ flags |= NULLPAD;
+ break;
+ case 'y':
+ flags |= RECYCLE;
+ break;
+ case 'H': /* print shape only */
+ flags |= DETAILSHAPE;
+ /* FALLTHROUGH */
+ case 'h':
+ flags |= SHAPEONLY;
+ break;
+ case 'z': /* squeeze col width */
+ flags |= SQUEEZE;
+ break;
+ /*case 'p':
+ ipagespace = atoi(optarg); (default is 1)
+ break;*/
+ default:
+ usage();
+ }
+
+ av += optind;
+ ac -= optind;
+
+ /*if (!osep)
+ osep = isep;*/
+ switch (ac) {
+#if 0
+ case 3:
+ opages = atoi(av[2]);
+ /* FALLTHROUGH */
+#endif
+ case 2:
+ val = strtol(av[1], NULL, 10);
+ if (val >= 0)
+ ocols = val;
+ /* FALLTHROUGH */
+ case 1:
+ val = strtol(av[0], NULL, 10);
+ if (val >= 0)
+ orows = val;
+ /* FALLTHROUGH */
+ case 0:
+ break;
+ default:
+ errx(1, "too many arguments");
+ }
+}
+
+static long
+getnum(const char *p)
+{
+ char *ep;
+ long val;
+
+ val = strtol(p, &ep, 10);
+ if (*ep != '\0')
+ errx(1, "invalid integer %s", p);
+ return (val);
+}
diff --git a/usr.bin/rs/tests/Makefile b/usr.bin/rs/tests/Makefile
new file mode 100644
index 000000000000..a11660690401
--- /dev/null
+++ b/usr.bin/rs/tests/Makefile
@@ -0,0 +1,3 @@
+ATF_TESTS_SH+= rs_test
+
+.include <bsd.test.mk>
diff --git a/usr.bin/rs/tests/rs_test.sh b/usr.bin/rs/tests/rs_test.sh
new file mode 100644
index 000000000000..cb53b08cde96
--- /dev/null
+++ b/usr.bin/rs/tests/rs_test.sh
@@ -0,0 +1,396 @@
+#
+# Copyright 2017 Shivansh
+# All rights reserved.
+#
+# 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.
+#
+#
+
+atf_test_case c_flag
+c_flag_head()
+{
+ atf_set "descr" "Verify the usage of option 'c'"
+}
+
+c_flag_body()
+{
+ atf_check -s exit:0 -o inline:"1 2 3 4
+5 6 7 8
+" rs -c, 2 <<EOF
+1,2,3,4,5,6,7,8
+EOF
+}
+
+atf_test_case s_flag
+s_flag_head()
+{
+ atf_set "descr" "Verify the usage of option 's'"
+}
+
+s_flag_body()
+{
+ atf_check -s exit:0 -o inline:"1 2 3 4
+5 6 7 8
+" rs -s% 2 <<EOF
+1%%2%%3%4%5%%%6%%%7%8
+EOF
+
+}
+
+atf_test_case C_flag
+C_flag_head()
+{
+ atf_set "descr" "Verify the usage of option 'C'"
+}
+
+C_flag_body()
+{
+ atf_check -s exit:0 -o inline:"1,2,3,4,
+5,6,7,8,
+" rs -C, 2 <<EOF
+1 2 3 4 5 6 7 8
+EOF
+}
+
+atf_test_case S_flag
+S_flag_head()
+{
+ atf_set "descr" "Verify the usage of option 'S'"
+}
+
+S_flag_body()
+{
+ atf_check -s exit:0 -o inline:"1,,2,,3,,4
+5,,6,,7,,8
+" rs -S, 2 <<EOF
+1 2 3 4 5 6 7 8
+EOF
+}
+
+atf_test_case t_flag
+t_flag_head()
+{
+ atf_set "descr" "Verify the usage of option 't'"
+}
+
+t_flag_body()
+{
+ atf_check -s exit:0 -o inline:"1 4 7
+2 5 8
+3 6
+" rs -t 3 <<EOF
+1 2 3 4 5 6 7 8
+EOF
+}
+
+atf_test_case T_flag
+T_flag_head()
+{
+ atf_set "descr" "Verify the usage of option 'T'"
+}
+
+T_flag_body()
+{
+ atf_check -s exit:0 -o inline:"1
+2
+3
+" rs -T <<EOF
+1 2 3
+EOF
+}
+
+atf_test_case k_flag
+k_flag_head()
+{
+ atf_set "descr" "Verify the usage of option 'k'"
+}
+
+k_flag_body()
+{
+ atf_check -s exit:0 -o inline:"3 4 5
+6 7 8
+" rs -k 1 2 <<EOF
+1 2
+3 4 5 6
+7 8
+EOF
+}
+
+atf_test_case K_flag
+K_flag_head()
+{
+ atf_set "descr" "Verify the usage of option 'K'"
+}
+
+K_flag_body()
+{
+ atf_check -s exit:0 -o inline:"
+" rs -K 1 < /dev/null
+}
+
+atf_test_case g_flag
+g_flag_head()
+{
+ atf_set "descr" "Verify the usage of option 'g'"
+}
+
+g_flag_body()
+{
+ atf_check -s exit:0 -o inline:"1 2 3 4
+5 6 7 8
+" rs -g 1 2 <<EOF
+1 2 3 4 5 6 7 8
+EOF
+}
+
+atf_test_case G_flag
+G_flag_head()
+{
+ atf_set "descr" "Verify the usage of option 'G'"
+}
+
+G_flag_body()
+{
+ atf_check -s exit:0 -o inline:"1 2 3 4
+5 6 7 acbdefghij
+" rs -G 50 2 <<EOF
+1 2 3 4 5 6 7 acbdefghij
+EOF
+}
+
+atf_test_case e_flag
+e_flag_head()
+{
+ atf_set "descr" "Verify the usage of option 'e'"
+}
+
+e_flag_body()
+{
+ atf_check -s exit:0 -o inline:"
+" rs -e < /dev/null
+}
+
+atf_test_case n_flag
+n_flag_head()
+{
+ atf_set "descr" "Verify the usage of option 'n'"
+}
+
+n_flag_body()
+{
+ atf_check -s exit:0 -o inline:"1 2 3
+4 5
+6 7
+" rs -n 0 3 <<EOF
+1 2 3
+4 5
+6 7
+EOF
+}
+
+atf_test_case y_flag
+y_flag_head()
+{
+ atf_set "descr" "Verify the usage of option 'y'"
+}
+
+y_flag_body()
+{
+ atf_check -s exit:0 -o inline:"1 2 3
+4 5 6
+7 8 1
+" rs -y 3 <<EOF
+1 2 3 4 5 6 7 8
+EOF
+}
+
+atf_test_case h_flag
+h_flag_head()
+{
+ atf_set "descr" "Verify the usage of option 'h'"
+}
+
+h_flag_body()
+{
+ atf_check -s exit:0 -o inline:"1 0
+" rs -h < /dev/null
+}
+
+atf_test_case H_flag
+H_flag_head()
+{
+ atf_set "descr" "Verify the usage of option 'H'"
+}
+
+H_flag_body()
+{
+ atf_check -s exit:0 -o inline:" 0 line 1
+1 0
+" rs -H < /dev/null
+}
+
+atf_test_case j_flag
+j_flag_head()
+{
+ atf_set "descr" "Verify the usage of option 'j'"
+}
+
+j_flag_body()
+{
+ atf_check -s exit:0 -o inline:" 1 2 3
+abc def ghi
+" rs -j 2 <<EOF
+1 2 3 abc def ghi
+EOF
+}
+
+atf_test_case m_flag
+m_flag_head()
+{
+ atf_set "descr" "Verify the usage of option 'm'"
+}
+
+m_flag_body()
+{
+ atf_check -s exit:0 -o inline:"1 2 3
+abc def ghi
+" rs -m 2 <<EOF
+1 2 3 abc def ghi
+EOF
+}
+
+atf_test_case z_flag
+z_flag_head()
+{
+ atf_set "descr" "Verify the usage of option 'z'"
+}
+
+z_flag_body()
+{
+ atf_check -s exit:0 -o inline:"1 2 3 4
+5 6 7 acbdefghij
+" rs -z 2 <<EOF
+1 2 3 4 5 6 7 acbdefghij
+EOF
+}
+
+atf_test_case invalid_usage
+invalid_usage_head()
+{
+ atf_set "descr" "Verify that an invalid usage with a supported option produces a valid error message"
+}
+
+invalid_usage_body()
+{
+ atf_check -s not-exit:0 -e inline:"rs: option requires an argument -- w
+usage: rs [-[csCS][x][kKgGw][N]tTeEnyjhHmz] [rows [cols]]
+" rs -w
+}
+
+atf_test_case no_arguments
+no_arguments_head()
+{
+ atf_set "descr" "Verify that rs(1) executes successfully and produces a valid output when invoked without any arguments"
+}
+
+no_arguments_body()
+{
+ atf_check -s exit:0 -o inline:"1 2 3 4 5 6 7 8
+" rs <<EOF
+1 2 3 4 5 6 7 8
+EOF
+}
+
+atf_test_case rows_2
+rows_2_head()
+{
+ atf_set "descr" "Simple output with 2 rows"
+}
+
+rows_2_body()
+{
+ atf_check -s exit:0 -o inline:"1 2 3 4
+5 6 7 8
+" rs 2 <<EOF
+1 2 3 4 5 6 7 8
+EOF
+}
+
+atf_test_case rows_3
+rows_3_head()
+{
+ atf_set "descr" "Simple output with 3 rows"
+}
+
+rows_3_body()
+{
+ atf_check -s exit:0 -o inline:"1 2 3
+4 5 6
+7 8
+" rs 3 <<EOF
+1 2 3 4 5 6 7 8
+EOF
+}
+
+atf_test_case rows_4
+rows_4_head()
+{
+ atf_set "descr" "Simple output with 4 rows"
+}
+
+rows_4_body()
+{
+ atf_check -s exit:0 -o inline:"1 2
+3 4
+5 6
+7 8
+" rs 4 <<EOF
+1 2 3 4 5 6 7 8
+EOF
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case c_flag
+ atf_add_test_case s_flag
+ atf_add_test_case C_flag
+ atf_add_test_case S_flag
+ atf_add_test_case t_flag
+ atf_add_test_case T_flag
+ atf_add_test_case k_flag
+ atf_add_test_case K_flag
+ atf_add_test_case g_flag
+ atf_add_test_case G_flag
+ atf_add_test_case e_flag
+ atf_add_test_case n_flag
+ atf_add_test_case y_flag
+ atf_add_test_case h_flag
+ atf_add_test_case H_flag
+ atf_add_test_case j_flag
+ atf_add_test_case m_flag
+ atf_add_test_case z_flag
+ atf_add_test_case invalid_usage
+ atf_add_test_case no_arguments
+ atf_add_test_case rows_2
+ atf_add_test_case rows_3
+ atf_add_test_case rows_4
+}