'Color escape codes in pretty printed columns
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:
- Adjust the
columnparameters to make the alignment work with color codes - 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:

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.
Solution 1:[1]
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:

Solution 2:[2]
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:

Solution 3:[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

Solution 4:[4]
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 --helporcolumn_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!
Solution 5:[5]
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:
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | MD XF |
| Solution 2 | |
| Solution 3 | |
| Solution 4 | |
| Solution 5 | Community |

