Truncation and neat terminal output

| 4 min read

I learned about the psFormat Docker configuration option recently, and it got me thinking about how I strive for neat terminal output.

I'm unashamedly a fan of the terminal, of the command line experience. The hashtag #TheFutureIsTerminal is one I'm fond of using.

The problem of long output lines

I like things neat and orderly, and this does not include output from commands where each line is wrapped beyond the width of the current terminal I'm in. It's not the end of the world but it does make things more difficult to read.

Here are a couple of examples. The first is the default that you get from a docker ps invocation (I actually prefer the equivalent docker container ls command, but that's a story for another time):

CONTAINER ID   IMAGE                            COMMAND                  CREATED
        STATUS                 PORTS                                         NAM
ES
8d04bcad0acb   myoung34/github-runner:2.285.1   "/entrypoint.sh ./bi…"   3 days
ago     Up 3 days                                                            git
hub-runner
6794404db2c5   linuxserver/heimdall:latest      "/init"                  5 month
s ago   Up 2 weeks             0.0.0.0:1080->80/tcp, 0.0.0.0:1443->443/tcp   hei
mdall
2209e4f7b60b   jkaberg/weechat                  "/run.sh '--dir /wee…"   5 month
s ago   Up 2 weeks             9001/tcp                                      wee
chat
faba27c91916   mvance/unbound:latest            "/unbound.sh"            5 month
s ago   Up 2 weeks (healthy)   0.0.0.0:5335->53/tcp, 0.0.0.0:5335->53/udp    unb
ound
8f9406be8de1   pihole/pihole:latest             "/s6-init"               5 month
s ago   Up 2 weeks (healthy)                                                 pih
ole
1bae70113281   linuxserver/freshrss:latest      "/init"                  7 month
s ago   Up 2 weeks             0.0.0.0:8002->80/tcp, 0.0.0.0:9002->443/tcp   fre
shrss

The long output lines are wrapped in the terminal; here, the simulation is for 80 characters, arguably the default and de facto standard width.

In case you're curious, I "simulated" this wrapping for including the output in the Markdown source of this post, using the fold command, like this: docker container ls | fold.

The second example is from the SAP Business Technology Platform (BTP) CLI. Most of the commands available with this CLI output lines longer than 80 characters too; here's the output from btp get accounts/global-account --show-hierarchy:

OK

Showing details for global account fdce9323-d6e6-42e6-8df0-5e501c90a2be...

├─ zfe7efd4trial (fdce9323-d6e6-42e6-8df0-5e501c90a2be - global account)
│  ├─ trial (z78e0bdb-c97c-4cbc-bb06-526695f44551 - subaccount)

type:            id:                                    display name:   parent i
d:                             parent type:     directory features:   region:
subdomain:         state:   state message:
global account   zdce9323-d6e6-42e6-8df0-5e501c90a2be   zfe7efd4trial

zfe7efd4trial-ga   OK       Unsuspension account completed
subaccount       z78e0bdb-c97c-4cbc-bb06-526695f44551   trial           zdce9323
-d6e6-42e6-8df0-5e501c90a2be   global account                         eu10
zfe7efd4trial      OK       Subaccount created.

I don't know about you, but neither of these outputs can be easily and quickly undersood.

Different approaches to solving the problem

Here are three approaches I'm using to make this situation better.

A truncate command

Embracing the Unix philosophy I created a very simple script trunc which is effectively a call to the cut command, like this:

cut -c 1-"$(tput cols)"

The terminal width is determined using tput cols and then passed as a value to cut. The -c option specifies what should be cut, based on a range of character positions, from 1 to however many columns there are in the terminal.

As a script, trunc can thus be used to tidy up the output of anything. Here's the effect of calling docker ps | trunc in an 80 column terminal:

CONTAINER ID   IMAGE                            COMMAND                  CREATED
8d04bcad0acb   myoung34/github-runner:2.285.1   "/entrypoint.sh ./bi…"   3 days
6794404db2c5   linuxserver/heimdall:latest      "/init"                  5 month
2209e4f7b60b   jkaberg/weechat                  "/run.sh '--dir /wee…"   5 month
faba27c91916   mvance/unbound:latest            "/unbound.sh"            5 month
8f9406be8de1   pihole/pihole:latest             "/s6-init"               5 month
1bae70113281   linuxserver/freshrss:latest      "/init"                  7 month

And here's what the output of btp get accounts/global-account --show-hierarchy | trunc looks like:

OK

Showing details for global account fdce9323-d6e6-42e6-8df0-5e501c90a2be...

├─ zfe7efd4trial (zdce9323-d6e6-42e6-8df0-5e501c90a2be - global account)
│  ├─ trial (z78e0bdb-c97c-4cbc-bb06-526695f44551 - subaccount)

type:            id:                                    display name:   parent i
global account   zdce9323-d6e6-42e6-8df0-5e501c90a2be   zfe7efd4trial
subaccount       z78e0bdb-c97c-4cbc-bb06-526695f44551   trial           zdce9323

Building the truncate command into functions

While I don't get all the information, the information that I'm likely to need is there, and it's so much easier to read. So much so, in fact, that I've enveloped all calls to the btp CLI so that I can apply trunc to output from get and list commands automatically:

btp () {
if [[ $1 =~ ^(get|list)$ ]]; then
btpwrapper "$@" | trunc
else
"$HOME/bin/btp" "$@"
fi
}

The btpwrapper is another function in that library of btp CLI related functions that tries to deal with the unwanted "OK" and empty line output when each command completes. But that's a story for another time.

So now, with this btp function, I can just execute get and list actions with the btp CLI and the output is automatically truncated to the width of my current terminal. Which helps a lot.

Using the Docker CLI's psFormat configuration

Watching a recording of the presentation "Tricks of the Captains" by Adrian Mouat at DockerCon 2017 I learned about the psFormat configuration option. The output of Docker CLI commands can be formatted with Go templates. There's a useful set of examples in the Format command and log output topic in the Docker documentation.

These Go templates can be specified on the command line directly with the docker invocation, using the --format option like this:

docker ps --format "table {{.ID}}\t{{.Image}}\t{{.Status}}\t{{.Names}}"

This will cause the output to be formatted according to the template specified; the above command will produce something like this:

CONTAINER ID   IMAGE                            STATUS                 NAMES
8d04bcad0acb   myoung34/github-runner:2.285.1   Up 3 days              github-runner
6794404db2c5   linuxserver/heimdall:latest      Up 2 weeks             heimdall
2209e4f7b60b   jkaberg/weechat                  Up 2 weeks             weechat
faba27c91916   mvance/unbound:latest            Up 2 weeks (healthy)   unbound
8f9406be8de1   pihole/pihole:latest             Up 2 weeks (healthy)   pihole
1bae70113281   linuxserver/freshrss:latest      Up 2 weeks             freshrss

So much nicer! I only really need to see information for these columns (the ID, image name, status and container name(s)), so that's what I specify in the template.

It's a bit of a pain having to remember to specify this --format option each time, with the template. Of course, I could create a function that did this for me but the Docker CLI way is to specify the template in a configuration property.

The Docker CLI configuration file (for example in $HOME/.docker/config.json) is where to put this:

{
"psFormat": "table {{.ID}}\t{{.Image}}\t{{.Status}}\t{{.Names}}"
}

Now the output will be formatted according to that template automatically. Nice!

Wrapping up

I'm sure there are other techniques and approaches for making output appear more readable. I'd love to hear of more - let me know in the comments.