14

I have a tab-delimited text file which I send to column to "pretty print" a table.

Original file:

1<TAB>blablablabla<TAB>aaaa bbb ccc
2<TAB>blabla<TAB>xxxxxx
34<TAB>okokokok<TAB>zzz yyy

Using column -s$'\t' -t <original file>, I get

1  blablablabla aaaa bbb xxx
2  blabla       xxxxxx
34 okokokok     zzz yyy

as desired. Now I want to add colors to the columns. I tried to add the escape codes around each tab-delimited field in the original file. column successfully prints in color, but the columns are no longer aligned. Instead, it just prints the TAB separators verbatim.

The question is: how can I get the columns aligned, but also with unique colors?

I've thought of two ways to achieve this:

  1. Adjust the column parameters to make the alignment work with color codes
  2. Redirect the output of column to another file, and do a search+replace on the first two whitespace-delimited fields (the first two columns are guaranteed to not contain spaces; the third column most likely will contain spaces, but no TAB characters)

Problem is, I'm not sure how to do either of those two...

For reference, here is what I'm passing to column:

Original file with color codes

Note that the fields are indeed separated by TAB characters. I've confirmed this with od.

edit:

There doesn't seem to be an issue with the colorization. I already have the file shown above with the color codes working. The issue is column won't align once I send it input with escape codes. I am thinking of passing the fields without color codes to column, then copying the exact number of spaces column output between each field, and using that in a pretty print scheme.

4
  • I tried this using just "column -t my_file" and don't see any problem with alignment. Commented Nov 22, 2013 at 18:20
  • If FIELD3 has spaces in it, it won't work unless you specify a different delimiter. Even so, mine is not aligning the first two columns even though they contain no spaces. Here is the results of column -t my_file: i.imgur.com/w6i1aGn.png
    – tomocafe
    Commented Nov 22, 2013 at 18:26
  • Have to leave for today. Will dig more into this tomorrow.. There must a solution. (You could use some more mighty language like python or perl of course. But awk + column should work.. don't know why)
    – hek2mgl
    Commented Nov 22, 2013 at 19:41
  • I think it might just be my machine -- it seems to work for other people. I have a fairly old system: Linux version 2.6.18-238.el5 ([email protected]) (gcc version 4.1.2 20080704 (Red Hat 4.1.2-50)) #1 SMP Sun Dec 19 14:22:44 EST 2010
    – tomocafe
    Commented Nov 22, 2013 at 20:06

6 Answers 6

9

I would use awk for the colorization (sed can be used as well):

awk '{printf "\033[1;32m%s\t\033[00m\033[1;33m%s\t\033[00m\033[1;34m%s\033[00m\n", $1, $2, $3;}' a.txt 

and pipe it to column for the alignment:

... | column -s$'\t' -t

Output:

output

10
  • How do you preserve the spacing done by column? From your awk command, it looks like a static number of spaces.
    – tomocafe
    Commented Nov 22, 2013 at 18:34
  • If I understand, that awk command will produce the same output as file I posted above... but I already tried sending that to column and it doesn't align right with the escape codes.
    – tomocafe
    Commented Nov 22, 2013 at 18:41
  • Nope... still prints the TAB instead of aligning. Also, the third field has spaces in it, and this only prints the first word of it.
    – tomocafe
    Commented Nov 22, 2013 at 18:48
  • How can it be? I've tested with your input? Which terminal type are you using?
    – hek2mgl
    Commented Nov 22, 2013 at 18:49
  • Do you have the word <tab> as delimiter, not a real tab?
    – hek2mgl
    Commented Nov 22, 2013 at 18:51
9

I wrote a bash version of column (similar to the one from util-linux) which works with color codes:

#!/bin/bash
which sed >> /dev/null || exit 1

version=1.0b
editor="Norman Geist"
last="04 Jul 2016"

# NOTE: Brilliant pipeable tool to format input text into a table by 
# NOTE: an configurable seperation string, similar to column
# NOTE: from util-linux, but we are smart enough to ignore 
# NOTE: ANSI escape codes in our column width computation
# NOTE: means we handle colors properly ;-)

# BUG : none

addspace=1
seperator=$(echo -e " ")
columnW=()
columnT=()

while getopts "s:hp:v" opt; do
  case $opt in
s ) seperator=$OPTARG;;
p ) addspace=$OPTARG;;
v ) echo "Version $version last edited by $editor ($last)"; exit 0;;
h ) echo "column2 [-s seperator] [-p padding] [-v]"; exit 0;;
* ) echo "Unknow comandline switch \"$opt\""; exit 1
  esac
done
shift $(($OPTIND-1))

if [ ${#seperator} -lt 1 ]; then
  echo "Error) Please enter valid seperation string!"
  exit 1
fi

if [ ${#addspace} -lt 1 ]; then
  echo "Error) Please enter number of addional padding spaces!"
  exit 1
fi

#args: string
function trimANSI()
{
  TRIM=$1
  TRIM=$(sed 's/\x1b\[[0-9;]*m//g' <<< $TRIM); #trim color codes
  TRIM=$(sed 's/\x1b(B//g'         <<< $TRIM); #trim sgr0 directive
  echo $TRIM
}

#args: len
function pad()
{
  for ((i=0; i<$1; i++))
  do 
echo -n " "
  done
}

#read and measure cols
while read ROW
do
  while IFS=$seperator read -ra COLS; do
ITEMC=0
for ITEM in "${COLS[@]}"; do
  SITEM=$(trimANSI "$ITEM"); #quotes matter O_o
  [ ${#columnW[$ITEMC]} -gt 0 ] || columnW[$ITEMC]=0
  [ ${columnW[$ITEMC]} -lt ${#SITEM} ] && columnW[$ITEMC]=${#SITEM}
  ((ITEMC++))
done
columnT[${#columnT[@]}]="$ROW"
  done <<< "$ROW"
done

#print formatted output
for ROW in "${columnT[@]}"
do
  while IFS=$seperator read -ra COLS; do
ITEMC=0
for ITEM in "${COLS[@]}"; do
  WIDTH=$(( ${columnW[$ITEMC]} + $addspace ))
  SITEM=$(trimANSI "$ITEM"); #quotes matter O_o
  PAD=$(($WIDTH-${#SITEM}))

  if [ $ITEMC -ne 0 ]; then
    pad $PAD
  fi

  echo -n "$ITEM"

  if [ $ITEMC -eq 0 ]; then
    pad $PAD
  fi

  ((ITEMC++))
done
  done <<< "$ROW"
  echo ""
done

Example usage:

bold=$(tput bold)
normal=$(tput sgr0)
green=$(tput setaf 2)

column2 -s § << END
${bold}First Name§Last Name§City${normal}
${green}John§Wick${normal}§New York
${green}Max§Pattern${normal}§Denver
END

Output example:

enter image description here

5
  • I've tried column2 and it works!! Unfortunately not so fast as column. Here goes the times: column2 did 0.706s/0.599s/0.612s/0.622s while original column did 0.012s/0.009s/0.009s/0.010s. It makes difference when the user is waiting for the information to be shown. But.... as column won't work with color escape codes, I'll continue using color2 and +1 to the author. Commented Apr 30, 2017 at 3:18
  • Brilliant! Can I use this in an open source project I am working on? I just sent you a mail with more information :)
    – mfnalex
    Commented Dec 19, 2017 at 14:20
  • is this available on GitHub? if so you should provide a link, I'd love to give it a star :) Commented Oct 3, 2020 at 21:21
  • @HudsonSantos I made a Perl-based version of column2 that is way faster (column: 0m0,059s/0m0,015s/0m0,015s, while mine 0m0,092s/0m0,000s/0m0,000s). I'll post it on Github (making credit to this answer obviously) and share it with you guys Commented Dec 28, 2021 at 18:56
  • See my answer stackoverflow.com/a/70513399/8965861 Commented Dec 28, 2021 at 23:04
3

A solution using printf to format the ouput as well :

while IFS=$'\t' read -r c1 c2 c3; do
    tput setaf 1; printf '%-10s' "$c1"
    tput setaf 2; printf '%-30s' "$c2"
    tput setaf 3; printf '%-30s' "$c3"
    tput sgr0; echo
done < file

enter image description here

2
  • +1 for the tput command usage :) .. But the aligment isn't ok yet. You should pipe to column
    – hek2mgl
    Commented Nov 22, 2013 at 19:14
  • 4
    Still doesn't work piping to column... I think the issue here is with column tripping up on color codes, not the colorization itself.
    – tomocafe
    Commented Nov 22, 2013 at 19:18
3

Starting with the next version v2.40 of util-linux / column will handle text containing Fe Escape sequences from ANSI escape code properly.

With this change, programs that output text with ANSI sequences will be handled correctly.

2

2021 Updated BASH Answer

TL;DR

I really liked @NORMAN GEIST's answer but was way too slow for what i needed... So i coded my own version of his script, this time written in Perl (stdin looping and formatting) + Bash (only for presentation/help).

You can find the full code here with an explanation on how to use it.

It is comprehensive of:

  • A Bash column-like command interface (same parameters like -t, -s, -o)
  • Exaustive help with column_ansi --help or column_ansi -h
  • Option to horizontally center.

The actual "core" code can broken down to only the Perl part.

Background and differences

I needed to format a very long awk-generated colored output (more than 300 lines) into a nice table. I first thought of using column, but as i discovered it didn't take into consideration ANSI characters, since the output would come out not aligned.

After searching a bit on Google i found @NORMAN GEIST's interesting answer on SO which dynamically calculated the width of every single column in the output after removing the ANSI characters and THEN it built the table.

It was all good, but it was taking way too long to load (as someone pointed in the comments)...

So i tried to convert @NORMAN GEIST's column2 from bash to perl and my god if there was a change!

After trying out this version in my production script the time used to display data dropped from 30s to <1s!!

Enjoy!

2
  • 1
    Brillant, thanks for this solution! Commented Feb 22, 2022 at 15:43
  • Thanks @carlfriedrich :). I'm happy you liked it. Hope it will help you as much as it was useful to me Commented Feb 28, 2022 at 16:59
1

In my case, I wanted to selectively colorise values in a column depending on its value. Let's say I want okokokok to be green and blabla to be red.

I can do it such way (the idea is to colorise values of columns after columnisation):

GREEN_SED='\\033[0;32m'
RED_SED='\\033[0;31m'
NC_SED='\\033[0m' # No Color

column -s$'\t' -t <original file> | echo -e "$(sed -e "s/okokokok/${GREEN_SED}okokokok${NC_SED}/g" -e "s/blabla/${RED_SED}blabla${NC_SED}/g")"

Alternatively, with a variable:

DATA=$(column -s$'\t' -t <original file>)

GREEN_SED='\\033[0;32m'
RED_SED='\\033[0;31m'
NC_SED='\\033[0m' # No Color
echo -e "$(sed -e "s/okokokok/${GREEN_SED}okokokok${NC_SED}/g" -e "s/blabla/${RED_SED}blabla${NC_SED}/g" <<< "$DATA")"

Take a note of that additional backslash in values of color definitions. It is made for sed to not interpret an origingal backsash.

This is a result:

enter image description here

Not the answer you're looking for? Browse other questions tagged or ask your own question.