Turn Messy Command Output into Reliable Tables with column
Ragged command output is hard to read at 2am, and column tidies it into columns or a named table. This guide covers plain lists, named and aligned tables, JSON and trees, and dodges the two defaults that cause most surprises: columns are filled before rows, and empty input fields are treated differently from missing ones. It takes about ten minutes.
The route
Jump straight to the step you need, or tick off Done means at the end.
- You need: a shell and the
bsdextrautilspackage on this Ubuntu machine. - Versions: the installed executable reports util-linux 2.41.3, but the local manual page is labelled util-linux 2.39.3. Verify unusual behaviour against the command on the host where a script will run.
- Privileges: none. These examples only read input and write standard output, so no
sudo.
1. Arrange a simple list
Give column one item per line. With no options it reads standard input and fills columns before rows:
$ printf '%s\n' pear apple banana kiwi | column
pear apple banana kiwi
The exact gaps depend on the item widths and the available output width. Empty lines are ignored by default. You can name a file instead of using a pipe:
$ column /path/to/items.txt
Checkpoint
Run column --version. On this machine it prints column from util-linux 2.41.3. If yours differs, keep the input small and compare the local column(1) manual before putting the command in a script.
2. Fill rows first when reading across is clearer
--fillrows, also written -x, fills each row before starting the next column. It suits short menus or labels that the reader scans left to right:
$ printf '%s\n' pear apple banana kiwi | column --fillrows --output-width 20
pear apple
banana kiwi
The width option makes the example reproducible. Without it, an interactive terminal supplies its own width, while non-interactive output defaults to 80 columns. Output longer than a requested width is not truncated unless you ask for truncation in table mode.
3. Build a named table from records
Use --table when each input line holds fields. Add --table-columns to name the fields and create the header. Do not put a header line in the input unless you want it shown as data:
$ printf '%s\n' 'root 20G online' 'var 8G full' |
column --table --table-columns NAME,SIZE,STATE --table-right SIZE
NAME SIZE STATE
root 20G online
var 8G full
--table-right SIZE right-aligns the named column. The default output separator is two spaces. For a visible separator, set --output-separator:
$ printf '%s\n' 'root 20G online' 'var 8G full' |
column --table --table-columns NAME,SIZE,STATE --table-right SIZE \
--output-separator ' | '
NAME | SIZE | STATE
root | 20G | online
var | 8G | full
Tip
The backslash continues the shell command and is not part of the data. Keep field names free of spaces, because they form a comma-separated option argument.
4. Parse a delimiter without losing empty fields
For input split on a known character, use --separator or -s. This example keeps the empty middle field between the two colons:
$ printf '%s\n' 'root:20G:online' 'var::full' |
column --table --separator ':' --output-separator ' | '
root | 20G | online
var | | full
In table mode without explicit names, the command uses the input's fields but does not invent a header. The installed command treats -s as a non-greedy separator, as documented since util-linux 2.23. That matters when two delimiters sit side by side: do not swap this test for a whitespace pipeline and assume the empty value will survive.
Checkpoint
Look at the rendered line for var. There should be a blank field between the two output separators. If a later tool must tell blank from absent data, test that tool separately, because visual alignment is not a data-quality check.
5. Control width and decide whether truncation is safe
Use --output-width or -c when output goes to a terminal, log or fixture with a known width. When writing to files, the manual recommends unlimited (or 0) so an accidental terminal size cannot constrain the table:
$ printf '%s\n' 'root 20G online' 'var 8G full' |
column --table --table-columns NAME,SIZE,STATE --output-width unlimited \
> /tmp/storage-table.txt
$ sed -n '1,4p' /tmp/storage-table.txt
NAME SIZE STATE
root 20G online
var 8G full
Redirecting to /tmp is ordinary user work. The file is temporary and system cleanup may remove it. If a long cell must fit a fixed display, use --table-truncate with the relevant column, and treat the result as presentation only:
$ printf '%s\n' 'alice short' 'bob very-long-description' |
column --table --output-width 20 --table-truncate 2
name description
alice short
bob very-long-des
Warning
Do not truncate identifiers, paths or values that another command will parse. A shorter display is not a shorter value.
6. Export a table as JSON
--json requires --table-columns. Add --table-name when the JSON key should be explicit:
$ printf '%s\n' 'alpha 7' 'beta 9' |
column --json --table --table-columns name,count --table-name items
{
"items": [
{
"name": "alpha",
"count": "7"
},{
"name": "beta",
"count": "9"
}
]
}
Values are strings unless you define a column with a json=number or json=boolean attribute using --table-column. JSON output is a format conversion, not validation. Check that the source fields are in the expected order and that blank values are acceptable to the consumer.
7. Print a tree from parent and child IDs
For records with an ID, a parent ID and a label, use --tree-id, --tree-parent and --tree. Column numbers are one-based:
$ printf '%s\n' '1 0 root' '2 1 child' '3 1 other' '4 2 leaf' |
column --tree-id 1 --tree-parent 2 --tree 3
1 0 root
2 1 ├─child
4 2 │ └─leaf
3 1 └─other
The tree is only a view of the relationships. According to the manual, circular dependencies and other anomalies are silently ignored, so a successful display does not prove the input graph is valid. Validate IDs and parent references separately when the data matters.
Common traps
- Header appears twice. Remove the header from the input, or stop passing
--table-columns. The option creates the named header itself. - Fields shift. Check the real delimiter and quote your separator argument.
- A line disappears. Empty lines are ignored unless you add
--keep-empty-linesor-L.
Warning
Do not overwrite a useful report with plain shell redirection. The > operator truncates its destination before column runs. Write to a new temporary path, inspect it, and replace the old file only if that is intended:
$ column --table --table-columns NAME,SIZE,STATE /path/to/input.txt > /tmp/report.new
$ test -s /tmp/report.new && mv /tmp/report.new /path/to/report.txt
Recovery
If the command fails, the old report is untouched. Remove the temporary file with rm /tmp/report.new only after checking its exact path, because that deletion cannot be undone through column.
Done means
- Filling chosen. You can arrange a list and pick column-first or row-first filling deliberately.
- Tables named. You can name table columns, align numeric-looking fields and set an output separator.
- Empty fields kept. You preserve delimiter-separated empty fields and know whitespace parsing is a different case.
- Width explicit. You set output width when reproducibility matters, and truncate only display-safe fields.
- Structured output checked. You use JSON and tree output only after verifying the input field order and relationships.
- Reports replaced safely. You write to a new path before moving a report into place.