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
+10 -1
View File
@@ -25,7 +25,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE. .\" SUCH DAMAGE.
.\" .\"
.Dd July 29, 2004 .Dd May 13, 2025
.Dt COLUMN 1 .Dt COLUMN 1
.Os .Os
.Sh NAME .Sh NAME
@@ -35,6 +35,7 @@
.Nm .Nm
.Op Fl tx .Op Fl tx
.Op Fl c Ar columns .Op Fl c Ar columns
.Op Fl l Ar tblcols
.Op Fl s Ar sep .Op Fl s Ar sep
.Op Ar .Op Ar
.Sh DESCRIPTION .Sh DESCRIPTION
@@ -53,6 +54,14 @@ The options are as follows:
Output is formatted for a display Output is formatted for a display
.Ar columns .Ar columns
wide. 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 .It Fl s
Specify a set of characters to be used to delimit columns for the Specify a set of characters to be used to delimit columns for the
.Fl t .Fl t
+35 -10
View File
@@ -54,6 +54,7 @@ static void usage(void);
static int width(const wchar_t *); static int width(const wchar_t *);
static int termwidth = 80; /* default terminal width */ static int termwidth = 80; /* default terminal width */
static int tblcols; /* number of table columns for -t */
static int entries; /* number of records */ static int entries; /* number of records */
static int eval; /* exit value */ static int eval; /* exit value */
@@ -68,7 +69,7 @@ main(int argc, char **argv)
FILE *fp; FILE *fp;
int ch, tflag, xflag; int ch, tflag, xflag;
char *p; char *p;
const char *src; const char *errstr, *src;
wchar_t *newsep; wchar_t *newsep;
size_t seplen; size_t seplen;
@@ -81,17 +82,26 @@ main(int argc, char **argv)
termwidth = win.ws_col; termwidth = win.ws_col;
tflag = xflag = 0; tflag = xflag = 0;
while ((ch = getopt(argc, argv, "c:s:tx")) != -1) while ((ch = getopt(argc, argv, "c:l:s:tx")) != -1)
switch(ch) { switch(ch) {
case 'c': 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; break;
case 's': case 's':
src = optarg; src = optarg;
seplen = mbsrtowcs(NULL, &src, 0, NULL); seplen = mbsrtowcs(NULL, &src, 0, NULL);
if (seplen == (size_t)-1) if (seplen == (size_t)-1)
err(1, "bad separator"); err(1, "bad separator");
newsep = malloc((seplen + 1) * sizeof(wchar_t)); newsep = calloc(seplen + 1, sizeof(wchar_t));
if (newsep == NULL) if (newsep == NULL)
err(1, NULL); err(1, NULL);
mbsrtowcs(newsep, &src, seplen + 1, NULL); mbsrtowcs(newsep, &src, seplen + 1, NULL);
@@ -110,6 +120,9 @@ main(int argc, char **argv)
argc -= optind; argc -= optind;
argv += optind; argv += optind;
if (tblcols && !tflag)
errx(1, "the -l flag cannot be used without the -t flag");
if (!*argv) if (!*argv)
input(stdin); input(stdin);
else for (; *argv; ++argv) else for (; *argv; ++argv)
@@ -217,7 +230,7 @@ maketbl(void)
int *lens, maxcols; int *lens, maxcols;
TBL *tbl; TBL *tbl;
wchar_t **cols; wchar_t **cols;
wchar_t *last; wchar_t *s;
if ((t = tbl = calloc(entries, sizeof(TBL))) == NULL) if ((t = tbl = calloc(entries, sizeof(TBL))) == NULL)
err(1, NULL); err(1, NULL);
@@ -226,9 +239,11 @@ maketbl(void)
if ((lens = calloc(maxcols, sizeof(int))) == NULL) if ((lens = calloc(maxcols, sizeof(int))) == NULL)
err(1, NULL); err(1, NULL);
for (cnt = 0, lp = list; cnt < entries; ++cnt, ++lp, ++t) { for (cnt = 0, lp = list; cnt < entries; ++cnt, ++lp, ++t) {
for (coloff = 0, p = *lp; for (p = *lp; wcschr(separator, *p); ++p)
(cols[coloff] = wcstok(p, separator, &last)); /* nothing */ ;
p = NULL) for (coloff = 0; *p;) {
cols[coloff] = p;
if (++coloff == maxcols) { if (++coloff == maxcols) {
if (!(cols = realloc(cols, ((u_int)maxcols + if (!(cols = realloc(cols, ((u_int)maxcols +
DEFCOLS) * sizeof(wchar_t *))) || DEFCOLS) * sizeof(wchar_t *))) ||
@@ -239,6 +254,16 @@ maketbl(void)
0, DEFCOLS * sizeof(int)); 0, DEFCOLS * sizeof(int));
maxcols += DEFCOLS; 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) if ((t->list = calloc(coloff, sizeof(*t->list))) == NULL)
err(1, NULL); err(1, NULL);
if ((t->len = calloc(coloff, sizeof(int))) == NULL) if ((t->len = calloc(coloff, sizeof(int))) == NULL)
@@ -319,8 +344,8 @@ width(const wchar_t *wcs)
static void static void
usage(void) usage(void)
{ {
(void)fprintf(stderr, (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); exit(1);
} }
+30
View File
@@ -144,6 +144,7 @@ seven.:eight.:nine
::zwei ::zwei
drei.. drei..
vier: vier:
:
END END
@@ -161,10 +162,39 @@ END
atf_check diff expected output 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_init_test_cases()
{ {
atf_add_test_case basic atf_add_test_case basic
atf_add_test_case rows atf_add_test_case rows
atf_add_test_case basic_table atf_add_test_case basic_table
atf_add_test_case colonic_table atf_add_test_case colonic_table
atf_add_test_case ncols
} }