15

Given perhaps comma- or tab-delimited input, I'd like to present a series of appropriately padded columns to stdout, so I can easily scan columnar information which would otherwise present rather messily.

I've tried troff-based solutions and while the simple demos have worked, feeding the command actual input has resulted in bizarre errors. I've currently resorted to using a sed-based method hack which is rather slow...

EDIT: column is quite a useful tool, however it'd be really awesome if I the columns had, say, a pipe character (|) between them so they do not appear to "float" in space and I can easily distinguish where each starts.

PS. This post's title used to read 'ASCII "table"', not 'ASCII-art table'. Edited to try and remove confusion.

Gilles 'SO- stop being evil'
  • 807,993
  • 194
  • 1,674
  • 2,175
i336_
  • 1,007
  • 1
  • 10
  • 28

6 Answers6

18

Assuming a CSV file, you can use column(1) like so:

column -ts, your_file

This is included in the bsdmainutils package on my Debian distribution, so I'm not sure how portable it is.

Two more things to note:

  1. The above example is simplistic; explore the man page for more details on how to format your output.
  2. It does not fare well with quoted fields containing commas. I.e., it would consider a,b,"c,d" as four columns not three.
Joseph R.
  • 38,849
  • 7
  • 107
  • 143
8

If the tabular data is read in python somehow (in this example reading csv file with the Pandas module), the "tabulate" module is quite useful and simple.

import pandas
from tabulate import tabulate

data = pandas.read_csv('/tmp/foo.csv', index_col=0)
print(tabulate(data, headers=data.columns, tablefmt="grid"))

Which gives a nice output:

+-----+------+------+
|     |    A |    B |
+=====+======+======+
| foo | 1    |  0.2 |
+-----+------+------+
| bar | 3.14 | 10.9 |
+-----+------+------+

You can produce different styles

print(tabulate(data, headers=data.columns, tablefmt="fancy_grid"))

╒═════╤══════╤══════╕
│     │    A │    B │
╞═════╪══════╪══════╡
│ foo │ 1    │  0.2 │
├─────┼──────┼──────┤
│ bar │ 3.14 │ 10.9 │
╘═════╧══════╧══════╛
billjoie
  • 181
  • 1
  • 2
7

This will columnate the input file, adding a | character to surround each column.

 sed -e 's/^/| /' -e 's/,/,| /g' -e 's/$/,|/' inputfile | column -t -s,

Sample run (using a readily-available colon-delimited file):

$ head -4 /etc/passwd | tr : , | \
  sed -e 's/^/| /' -e 's/,/,| /g' -e 's/$/,|/' | column -t -s,

| root    | x  | 0  | 0  | root    | /root      | /bin/bash  |
| daemon  | x  | 1  | 1  | daemon  | /usr/sbin  | /bin/sh    |
| bin     | x  | 2  | 2  | bin     | /bin       | /bin/sh    |
| sys     | x  | 3  | 3  | sys     | /dev       | /bin/sh    |
Mark Plotnick
  • 24,913
  • 2
  • 59
  • 81
3

With mlr (Miller, miller package in Debian-based systems):

mlr --icsv --opprint --barred cat < file.csv

See the mlr man page for how to specify the input format (--itsv for instance for tab separated input).

Example:

$ cat file.csv
foo,bar
x,1.2
y,1.3
$ mlr --icsv --opprint --barred cat < file.csv
+-----+-----+
| foo | bar |
+-----+-----+
| x   | 1.2 |
| y   | 1.3 |
+-----+-----+
Stéphane Chazelas
  • 522,931
  • 91
  • 1,010
  • 1,501
0

Based on the ideas above with a bit more to automatically add separator lines.

No Header Row

function csv_table () {
    table=$(echo "$1"|sed -e 's/^/,/' -e 's/$/,/' -e 's/,/,| /g' |column -t -s,);
    line=$(echo "$table" |head -n1 | sed -e 's/[^|]/-/g' -e 's/.$//' -e 's/|/+/g');
    echo ${line};
    echo "$table";
    echo ${line}
}

With Header Row

function csv_table_header () {
    table=$(echo "$1"|sed -e 's/^/,/' -e  's/$/,/' -e 's/,/,| /g' |column -t -s,)
    line=$(echo "$table" |head -n1 | sed -e 's/[^|]/-/g' -e 's/.$//' -e 's/|/+/g')
    echo ${line}
    echo "$table" | head -n1
    echo ${line}
    echo "${table}" | tail -n +2
    echo ${line}
}

Sample Data

some_csv_data='Column 1,Column 2,Column 3
1,2,3
apple,banana,cherry'

Output (no header)

$ csv_table "${some_csv_data}"
+-----------+-----------+-----------+
| Column 1  | Column 2  | Column 3  |
| 1         | 2         | 3         |
| apple     | banana    | cherry    |
+-----------+-----------+-----------+

Output (with header)

$ csv_table_header "${some_csv_data}"
+-----------+-----------+-----------+
| Column 1  | Column 2  | Column 3  |
+-----------+-----------+-----------+
| 1         | 2         | 3         |
| apple     | banana    | cherry    |
+-----------+-----------+-----------+
jabend
  • 1
  • 1
  • 1
    (1) We prefer answers with more explanation. (2) You should say `echo "$line"` rather than `echo ${line}`.  Or, if you *really* need to (and you can explain *why* you need to), `echo "${line}"`. (Or, really preferably, ```printf '%s\n' "$line"```.) (3) Your answer will choke a little if the header row (the first row of the input) contains any `|` characters. – G-Man Says 'Reinstate Monica' Jun 05 '22 at 06:48
0

Using Raku (formerly known as Perl_6)

raku -MTerminal::Table -e 'my @a = lines.map: *.split(",").list;  print-table(@a);' 

OR

raku -MText::Table::Simple -e 'my @a = lines.map: *.split(",").list;  .say for lol2table(@a[0], @a[1..*]);'

Sample Input:

Column 1,Column 2,Column 3
1,2,3
apple,banana,cherry

Sample Output (using the Terminal::Table module):

+--------+--------+--------+
|Column 1|Column 2|Column 3|
+--------+--------+--------+
|1       |2       |3       |
+--------+--------+--------+
|apple   |banana  |cherry  |
+--------+--------+--------+

Sample Output (using the Text::Table::Simple module):

O----------O----------O----------O
| Column 1 | Column 2 | Column 3 |
O==========O==========O==========O
| 1        | 2        | 3        |
| apple    | banana   | cherry   |
----------------------------------

Above, lines are individually split on commas, converted to list format, and sent to module functions for printing.

Note: it's probably best to use a dedicated CSV parser for anything other that a 'simple-CSV' file (no commas-inside-quotations, no embedded newlines, etc.). Below is an example using Raku's Text::CSV module. Observe how fields are properly quoted in the output, and commas-within-doublequotes remain untouched:

~$ cat ./date_durian.csv
Column 1,Column 2,Column 3,Column 4
1,2,3,4
apple,banana,cherry,"date, durian"
~$ raku -MText::CSV -e 'my @a = csv(in => $*IN, sep => ",") andthen csv(in => $_, out => $*OUT);'  < ./date_durian.csv
"Column 1","Column 2","Column 3","Column 4"
1,2,3,4
apple,banana,cherry,"date, durian"
~$ raku -MText::CSV -MText::Table::Simple -e 'my @a = csv(in => $*IN); .say for lol2table(@a[0], @a[1..*]);' < ./date_durian.csv
O----------O----------O----------O--------------O
| Column 1 | Column 2 | Column 3 | Column 4     |
O==========O==========O==========O==============O
| 1        | 2        | 3        | 4            |
| apple    | banana   | cherry   | date, durian |
-------------------------------------------------

https://modules.raku.org/search/?q=CSV
https://modules.raku.org/t/TABLE
https://raku.org/

jubilatious1
  • 2,385
  • 8
  • 16