14

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.

6 Answers 6

17

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.
2
  • Combining it with head is useful: column -ts, your_file | head
    – Abramodj
    Oct 18, 2018 at 18:46
  • "column -tns, file" works better when the head line has a blank cell left-side. Nov 20, 2021 at 20:54
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 │
╘═════╧══════╧══════╛
1
  • You can run the suggested code inside ipython (run ipython in your shell to start it)
    – Abramodj
    Oct 18, 2018 at 18:35
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    |
0
2

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 |
+-----+-----+
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    |
+-----------+-----------+-----------+
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. Jun 5, 2022 at 6: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/

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.