3

I would like to distinguish adjacent matches more easily, while still retaining the context of input. In order to do so, it would be nice to cycle through a list of colors for each match found by grep.

I would like to modify the command

echo -e "AA\nAABBAABBCC\nBBAABB" | grep --color=always "AABB\|"

So that instead of printing:

Red only

It would print:

Red and blue

Can this be done in grep? The closest I answer I could find was matching two different (and non-overlapping) grep queries in different colors.

Alternatively, how can I most easily get this functionality in an Ubuntu terminal?

Community
  • 1
  • 1
AffluentOwl
  • 3,337
  • 5
  • 22
  • 32

3 Answers3

2

You can achieve a grep with color rotation using perl and a single substitution using an experimental regex feature to wrap each occurrence in ANSI escape sequences. Here's something to serve as a starting point that you can wrap in a shell function:

$ printf "FOOBARFOOFOOFOOBAR\nFOOBARFOOFOOFOOBARFOOFOOFOOFOOFOOFOOFOOFOOFOOFOOFOOFOO\n" \
    | perl -ne 'next unless /FOO/; $m=0; s#(?<!\[0mFOO)\K((?{$n=30+(++$m%8)})FOO)#\033\[1;${n}m\1\033\[0m#g; print'

Not very pretty but at least brief.

I'm too lazy to redo the screenshot but you might want to skip the dark grey by doing $n = 31 + (++$m % 7) instead. If you only want two colors set the divisor to 2 (obviously).

enter image description here

Adrian Frühwirth
  • 42,970
  • 10
  • 60
  • 71
1

Awk

This can be achieved with an awk script which utilizes ANSI escape sequences

#!/usr/bin/awk -f
# USAGE: echo -e "AA\nAABBAABBCC\nBBAABB" | awk -f color_grep.awk -v regex="AABB"

BEGIN {
  # Bold Red ANSI Code
  c[0] = "\x1b[1;31m"
  # Bold Blue ANSI Code
  c[1] = "\x1b[1;34m"
  # Default ANSI Code
  n = "\x1b[0m"
}

{
  i--
  j = 1

  do {
    temp = $0;
    i = (i + 1) % 2
    $0 = gensub("(" regex ")", c[i] "\\1" n, j, temp);
    j++
  } while ($0 != temp)

  print $0
}

Or as a one liner on the command line:

echo -e "AA\nAABBAABBCC\nBBAABB" | awk 'BEGIN { c[0]="\x1b[1;31m"; c[1]="\x1b[1;34m"; n="\x1b[0m"} { i--; j=1; do { $0=gensub(/(AABB)/, c[i=(i+1)%2] "\\1" n, j++, temp=$0); } while ($0!=temp) print $0 }'


Perl

After seeing Adrian's answer, I decided to come up with my own perl solution.

#!/usr/bin/perl
# USAGE: echo -e "AA\nAABBAABBCC\nBBAABB" | ~/color_grep.perl "AABB"

$regex = @ARGV[0];

# Generates ANSI escape sequences for bold text colored as follows:
# 0 - Red, 2 - Green, 3- Yellow, 4 - Blue, 5 - Magenta, 6 - Cyan
sub color { "\033\[1;" . (31 + $_[0] % 6) . "m" }

# ANSI escape sequence for default text
$default = "\033\[0m";

while (<STDIN>) {
  # Surround the matched expression with the color start and color end tags.
  # After outputting each match, increment to the next color index
  s/($regex)/color($i++) . $1 . $default/ge;
  print;
}

As a one liner:

printf "FOOBARFOOFOOFOOBAR\nFOOBARFOOFOOFOOBARFOOFOOFOOFOOFOOFOOFOOFOOFOOFOOFOOFOO\n" | perl -ne 'BEGIN{sub c {"\033\[1;".(31+$_[0]%6)."m"} $d="\033\[0m";} s/(FOO)/c($i++).$1.$d/ge; print'

enter image description here

AffluentOwl
  • 3,337
  • 5
  • 22
  • 32
1

You can use colout: http://nojhan.github.io/colout/

This example will colorize your pattern in text stream cycling through rainbow colors.

echo -e "AA\nAABBAABBCC\nBBAABB" | colout AABB rainbow

You can change rainbow to random, use some other color map or define it on the fly:

echo -e "AA\nAABBAABBCC\nBBAABB" | colout -c AABB red,blue

-c option instructs colout to cycle comma-separated colors at each match using them as a color map.

user2683246
  • 3,399
  • 29
  • 31