How can a space-delimited list of words be folded into tabular columns that fit in the terminal's width
It looks like you'd need to get the total width in all possible number of columns (from 2 to COLUMNS/2) to determine the width of each column and which is the maximum number of columns where that can fit.
With perl
:
#! /usr/bin/perl
use List::Util qw(sum);
$/ = undef;
@word = <> =~ /\S+/g;
$max = $ENV{COLUMNS}/2;
for ($i = 0; $i <= $#word; $i++) {
$l = length $word[$i];
for ($c = 2; $c <= $max; $c++) {
if ($l > $w[$c][$i%$c]) {
$w[$c][$i%$c] = $l
}
}
}
for ($c = $max; $c > 1; $c--) {
last if $c + sum(@{$w[$c]}) - 1 <= $ENV{COLUMNS}
}
if($c > 1) {
@w = @{$w[$c]};
for ($i = 0; $i <= $#word; $i++) {
if (($i+1)%$c && $i < $#word) {
printf "%-*s ", $w[$i%$c], $word[$i]
} else {
print "$word[$i]\n"
}
}
} else {
print "$_\n" for @word
}
Example:
$ lorem -w 50 | COLUMNS=60 that-script
minima aut veritatis laudantium qui voluptatem
est nostrum quis enim placeat hic
voluptas ab ratione sit hic sit
pariatur et provident voluptas aut odio
aut vero atque voluptatem amet voluptatem
ipsum iusto omnis tenetur ratione ratione
illo ea odit excepturi quisquam aut
nobis porro incidunt corrupti maxime ad
est sunt
For non-ASCII text, see Get the display width of a string of characters to determine the display width of a string. Something like:
#! /usr/bin/perl
use Text::CharWidth qw(mbswidth);
use List::Util qw(sum);
$/ = undef;
@word = <> =~ /\S+/g;
$max = $ENV{COLUMNS}/2;
for ($i = 0; $i <= $#word; $i++) {
$l = mbswidth $word[$i];
for ($c = 2; $c <= $max; $c++) {
if ($l > $w[$c][$i%$c]) {
$w[$c][$i%$c] = $l
}
}
}
for ($c = $max; $c > 1; $c--) {
last if $c + sum(@{$w[$c]}) - 1 <= $ENV{COLUMNS}
}
if($c > 1) {
@w = @{$w[$c]};
for ($i = 0; $i <= $#word; $i++) {
if (($i+1)%$c && $i < $#word) {
printf $word[$i] . " " x ($w[$i%$c]+1-mbswidth($word[$i]))
} else {
print "$word[$i]\n"
}
}
} else {
print "$_\n" for @word
}
To produce equally spaced columns, you could use BSD rs
(also ported to Debian and derivatives (at least) and available as a package there) or BSD column
(in the bsdmainutils
package on Debian):
tr -s '[:space:]' '[ *]' | rs -w "$COLUMNS"
tr -s '[:space:]' '[\n*]' | column -xc "$COLUMNS"
Example (the vertical line is to show the edge of that 60 column wide screen, it is not part of the output):
$ lorem -w 30 | tr -s '[:space:]' '[ *]' | rs -w60
earum aspernatur ipsa sed ┃
quod sit esse quisquam ┃
animi reprehenderit porro et ┃
delectus neque esse quia ┃
pariatur amet iste voluptatem ┃
provident praesentium et sint ┃
quo animi doloribus veritatis ┃
iusto alias ┃
With rs
, You can add the -z
option to reduce the space between the columns, but that does not optimise the number of columns accordingly. For instance, on the above, it gives (with rs -zw60
):
earum aspernatur ipsa sed ┃
quod sit esse quisquam ┃
animi reprehenderit porro et ┃
delectus neque esse quia ┃
pariatur amet iste voluptatem ┃
provident praesentium et sint ┃
quo animi doloribus veritatis ┃
iusto alias ┃
Instead of:
earum aspernatur ipsa sed quod ┃
sit esse quisquam animi reprehenderit ┃
porro et delectus neque esse ┃
quia pariatur amet iste voluptatem ┃
provident praesentium et sint quo ┃
animi doloribus veritatis iusto alias ┃
It also doesn't work with multi-byte characters or 0-width or double-width characters.
By default, it leaves at least 2 spaces between the columns. You can change it to 1 with -g 1
.