column(1): add -l flag

the '-l <tblcols>' flag limits the number of columns that column(1) will
produce in -t mode.  this is syntax-compatible with the same option in
util-linux's column(1), but due to existing differences between the two
implementations, it's not semantically compatible.

as a side-effect, fix a pre-existing bug where empty fields could cause
incorrect output:

	% echo ':' | column -ts:
	(null)

while here, also fix a couple of minor existing issues.

Reviewed by:	des
Approved by:	des (mentor)
Differential Revision:	https://reviews.freebsd.org/D50290
This commit is contained in:
Lexi Winter 2025-05-13 11:28:59 +01:00
parent 3bcf8e6db8
commit 313713b24c
3 changed files with 75 additions and 11 deletions

View file

@ -25,7 +25,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.Dd July 29, 2004
.Dd May 13, 2025
.Dt COLUMN 1
.Os
.Sh NAME
@ -35,6 +35,7 @@
.Nm
.Op Fl tx
.Op Fl c Ar columns
.Op Fl l Ar tblcols
.Op Fl s Ar sep
.Op Ar
.Sh DESCRIPTION
@ -53,6 +54,14 @@ The options are as follows:
Output is formatted for a display
.Ar columns
wide.
.It Fl l
When used with
.Fl t ,
limit the table to
.Ar tblcols
columns in width.
The last column will contain the rest of the line,
including any delimiters.
.It Fl s
Specify a set of characters to be used to delimit columns for the
.Fl t

View file

@ -54,6 +54,7 @@ static void usage(void);
static int width(const wchar_t *);
static int termwidth = 80; /* default terminal width */
static int tblcols; /* number of table columns for -t */
static int entries; /* number of records */
static int eval; /* exit value */
@ -68,7 +69,7 @@ main(int argc, char **argv)
FILE *fp;
int ch, tflag, xflag;
char *p;
const char *src;
const char *errstr, *src;
wchar_t *newsep;
size_t seplen;
@ -81,17 +82,26 @@ main(int argc, char **argv)
termwidth = win.ws_col;
tflag = xflag = 0;
while ((ch = getopt(argc, argv, "c:s:tx")) != -1)
while ((ch = getopt(argc, argv, "c:l:s:tx")) != -1)
switch(ch) {
case 'c':
termwidth = atoi(optarg);
termwidth = strtonum(optarg, 0, INT_MAX, &errstr);
if (errstr != NULL)
errx(1, "invalid terminal width \"%s\": %s",
optarg, errstr);
break;
case 'l':
tblcols = strtonum(optarg, 0, INT_MAX, &errstr);
if (errstr != NULL)
errx(1, "invalid max width \"%s\": %s",
optarg, errstr);
break;
case 's':
src = optarg;
seplen = mbsrtowcs(NULL, &src, 0, NULL);
if (seplen == (size_t)-1)
err(1, "bad separator");
newsep = malloc((seplen + 1) * sizeof(wchar_t));
newsep = calloc(seplen + 1, sizeof(wchar_t));
if (newsep == NULL)
err(1, NULL);
mbsrtowcs(newsep, &src, seplen + 1, NULL);
@ -110,6 +120,9 @@ main(int argc, char **argv)
argc -= optind;
argv += optind;
if (tblcols && !tflag)
errx(1, "the -l flag cannot be used without the -t flag");
if (!*argv)
input(stdin);
else for (; *argv; ++argv)
@ -217,7 +230,7 @@ maketbl(void)
int *lens, maxcols;
TBL *tbl;
wchar_t **cols;
wchar_t *last;
wchar_t *s;
if ((t = tbl = calloc(entries, sizeof(TBL))) == NULL)
err(1, NULL);
@ -226,9 +239,11 @@ maketbl(void)
if ((lens = calloc(maxcols, sizeof(int))) == NULL)
err(1, NULL);
for (cnt = 0, lp = list; cnt < entries; ++cnt, ++lp, ++t) {
for (coloff = 0, p = *lp;
(cols[coloff] = wcstok(p, separator, &last));
p = NULL)
for (p = *lp; wcschr(separator, *p); ++p)
/* nothing */ ;
for (coloff = 0; *p;) {
cols[coloff] = p;
if (++coloff == maxcols) {
if (!(cols = realloc(cols, ((u_int)maxcols +
DEFCOLS) * sizeof(wchar_t *))) ||
@ -239,6 +254,16 @@ maketbl(void)
0, DEFCOLS * sizeof(int));
maxcols += DEFCOLS;
}
if ((!tblcols || coloff < tblcols) &&
(s = wcspbrk(p, separator))) {
*s++ = L'\0';
while (*s && wcschr(separator, *s))
++s;
p = s;
} else
break;
}
if ((t->list = calloc(coloff, sizeof(*t->list))) == NULL)
err(1, NULL);
if ((t->len = calloc(coloff, sizeof(int))) == NULL)
@ -319,8 +344,8 @@ width(const wchar_t *wcs)
static void
usage(void)
{
(void)fprintf(stderr,
"usage: column [-tx] [-c columns] [-s sep] [file ...]\n");
"usage: column [-tx] [-c columns] [-l tblcols]"
" [-s sep] [file ...]\n");
exit(1);
}

View file

@ -144,6 +144,7 @@ seven.:eight.:nine
::zwei
drei..
vier:
:
END
@ -161,10 +162,39 @@ END
atf_check diff expected output
}
atf_test_case "ncols"
ncols_head()
{
atf_set descr "column(1) with -t (table) and -s and -l options"
}
ncols_body()
{
cat >input <<END
now we have five columns
here there are four
now only three
just two
one
END
cat >expected <<END
now we have five columns
here there are four
now only three
just two
one
END
atf_check -o save:output column -tc120 -l3 input
atf_check diff expected output
}
atf_init_test_cases()
{
atf_add_test_case basic
atf_add_test_case rows
atf_add_test_case basic_table
atf_add_test_case colonic_table
atf_add_test_case ncols
}