Format text into tables with the column command
I needed a clean ASCII table for a status report on Friday and discovered I had been doing it the hard way for years. I was typing the pipes by hand, lining up widths in my head, and getting it wrong twice before I got it right. Then I tried the column command. It took about 90 seconds to learn and it has saved me probably two hours of fiddling since. This is the post I wish I had read a year ago.
The column utility has been in the Linux coreutils (a collection of basic file, shell, and text manipulation tools that ship with every Unix-like system) package since before I was using Linux. It is one of those commands that sits in the coreutils tree next to cat and sort and gets ignored because nobody explains what it is for. What it is for, mostly, is making tables out of text that is not yet a table.
The simplest case: tab-delimited text
Most of the data I have lying around in plain text is delimited by tabs. Log files, CSV exports, the output of some CLI tools. The column command turns that into a neat table with one flag.
cat data.txt | column -t -s $'\t'
That is the whole trick. The -t flag tells column to make a table. The -s $'\t' tells it the input is tab-delimited. The $'\t' is the bash syntax for a literal tab character. Pipe the result into less if it is long, and you have a table that is actually readable.
The reason this matters is that most terminal-friendly data formats stop being readable the moment they have more than two columns. A two-column CSV is fine. A six-column CSV is a wall of commas. The column command solves this in one line and I had no idea.
The case I actually use: pretty-printing a custom report
Most of my real use is not for log files. It is for status reports I write by hand. I keep a list of projects, their owners, the date of the last deploy, and a one-line summary. For a long time I formatted this list as a Markdown table, which is fine in a Markdown renderer and awful in a Slack message. The Slack message version was always a mess of pipes.
Here is the workflow I settled on. I write the data in a tab-separated file called status.tsv. Three columns: name, owner, last deploy date. Then I run:
column -t -s $'\t' status.tsv
The output is a real table. It pastes into Slack without breaking. It pastes into an email without breaking. It renders correctly in a terminal window that is 100 columns wide and one that is 80 columns wide. The reason is that column does not pad with spaces, it pads with the right amount of spaces for the longest entry in each column, which means the table looks reasonable in most terminal widths.
The trade-off is that very long entries still get wrapped, and the wrapping is ugly. If you have a column with prose in it, do not use column for it. Use a real table renderer or just paste it into Markdown.
Other flags worth knowing
The column command has more flags than the two I use, and most of them are worth a sentence.
-c(column count). Tellscolumnto use a specific output width. Useful when you want a fixed-width table that does not depend on terminal size.-n(no column names). By default,columntreats the first row as a header. With-n, every row is a data row. I use this for status reports where I do not want a header.-J(JSON output). Available in newer versions ofcolumn. Renders the table as a JSON array. I have not found a use for this yet but I want one.-d(delimiter for empty fields). Lets you specify what to use for empty fields. Defaults to empty string. I have never needed this but the option exists.
The most useful of these for me is -c, because I write status reports for clients with different terminal widths. Forcing a 90-column output means the table looks the same on every machine I paste it into.
A shell function I added to my dotfiles
I added a small shell function to my shell config so I do not have to remember the flags. It is six lines and I use it two or three times a week.
tbl() {
column -t -s $'\t' "$@" | less -SR
}
Now tbl status.tsv opens a neat table in less with my preferred options. The whole setup is shorter than the manual formatting I was doing by hand, which is the part that still amuses me.
Trade-offs
The column command is not a real table renderer. If you need to embed the table in a document, render it as HTML, or sort it, you want a real tool. csvkit is the right answer for serious CSV work. pandoc is the right answer for turning tables into HTML or LaTeX. column is the right answer for “I have tab-separated text and I need to look at it in my terminal for 30 seconds.” It is exceptionally good at that one job.
Bash-specific $'\t' syntax for tab characters is a small annoyance if you are writing a script that needs to run on both bash and sh. The portable way is to use a literal tab, but that does not survive copy-paste very well. I have made peace with bash-only column invocations in my own scripts.
My shell function uses less -SR, which is opinionated. The -S flag turns off line wrapping, which means long lines are cut off instead of wrapped. For tables this is almost always what you want. For other uses it is annoying. The tbl function I wrote is for tables. If you want a more general purpose column wrapper, drop the -S.
A small thing I almost forgot
The first time I tried column, I assumed it would handle CSV files with quoted strings and embedded commas. It does not. If your CSV has commas inside quoted fields, column will split on every comma and your table will be wrong. The tool is for tab-separated text, not for CSV. If you have CSV with embedded commas, use csvkit or csvlook, both of which handle the quoting correctly. I made this mistake once and spent 20 minutes wondering why my table had extra columns before I read the man page. The fix at the time was to run the file through tr ',' '\t' first, which collapses the embedded commas into single tabs and lets column do the rest. That workaround is fragile, and I would not recommend it for production data.
What I would tell past me
If I could send a message back to the version of me that was typing pipe characters into Slack messages by hand, I would say three things.
- Try
column -t -s $'\t'on a tab-separated file before you format it manually. It is 10 seconds of typing that will save you 10 minutes of fiddling. - Keep your data in a tab-separated format even when the destination is Markdown. Markdown is fine for rendering, painful for editing. Tabs are great for editing, fine for rendering with
column. - Do not reach for
csvkitorpandasuntil you have hit the limit ofcolumn. The limit is real. I hit it about three months after I started usingcolumn. The three months were a useful delay.