4

I would like to output a series of space-delimited words in a tabular format, filling line by line so that nothing exceeds the terminal's width but the available space is used optimally, like this:

+-----------------------------------------------+
|polite      babies      embarrass   rightful   | 
|aspiring    scandalous  mut         disgusted  |
|bell        deeply      writer      jumbled    |
|respired    craggy                             |

(the box illustrates the terminal's width - it is not part of the output)

The commands that spring to mind are fold and column in a pipeline like this:

$ fold words -s -w $COLUMNS | column -t

This almost works but the output ends up wider than $COLUMNS (the terminal width) because it is first folded within that width and then the whitespace is stretched to line them up.

What I need is the effect of both in one. Are there any command-line tools (or shell built-ins) that can do this?

  • Do you want your columns to all have the same width or just that there be at least 1 space between the columns? – Stéphane Chazelas Nov 18 '16 at 13:37
  • at least one space would suffice – starfry Nov 18 '16 at 13:44
3

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.

  • This looks interesting, I found the Debian package but it looks like I'll have to roll it into an ArchLinux package myself so that I can try it out. – starfry Nov 18 '16 at 19:59
  • I've tried this on ArchLinux (AUR package rs-git). It would appear to be what I was looking for. – starfry Nov 21 '16 at 12:50
4

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
}
  • I will try this out, I hoped there would be a simple command that I could use in a bash script, but I guess this could be the simple command :) – starfry Nov 18 '16 at 14:48
  • @starfry. I've got the feeling that there is one, I vaguely remember something about it. You may want to wait a bit. There's also print -rC4 -- $words in zsh to print 4 columns. – Stéphane Chazelas Nov 18 '16 at 15:17
  • Well I tried both scripts, worked as expected, thank you. Before I could use the latter, I had to pacman -S perl-text-charwidth. – starfry Nov 18 '16 at 20:13
  • @StéphaneChazelas, it's looks like what 'ls -C' does, I have no idea how 'ls -C' functionality can be use to parse content of a file(?) – αғsнιη Nov 20 '16 at 7:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.