k-a-v
k-a-v

Reputation: 347

Sed replace regex with regex

Given the following file.txt:

this is line 1
# this is line 2
this is line 3

I would like to use sed to replace the lines with # at the beginning with \e[31m${lineContent}\e[0m. This will color that particular line. Additionally, I need the color \e[31m to be in a variable, color. (The desired output of this example would be having the second line colored). I have the following:

function colorLine() {
    cat file.txt | sed ''/"$1"/s//"$(printf \e[31m $1 \e[0m)"/g''
}
colorLine "#.*"

The variable color is not included in what I have so far, as I am not sure how to go about that part. The output of this is (with the second line being red):

this is line 1
#.*
this is line 3

It is apparently interpreting the replace string literally. My question is how do I use the matched line to generate the replace string?

I understand that I could do something much easier like appending \e[31m to the beginning of all lines that start with #, but it is important to use sed with the regexes.

Upvotes: 1

Views: 337

Answers (4)

Jonathan Leffler
Jonathan Leffler

Reputation: 755016

colorLine() {
    sed "/$1/s//"$'\e[31m&\e[0m/' file.txt
}
colorLine "#.*"

Multiple fixes, but it uses $1 to identify the pattern from the arguments to the function, and then uses ANSI-C quoting to encode the escape sequences — and fixes the color reset sequence which was (originally) missing the [ after the escape sequence. It also avoids the charge of "UUoC — Useless Use of cat".

The fixed file name is not exactly desirable, but fixing it is left as an exercise for the reader.


What if I needed \e[31m to be a variable, $color. How do I change the quoting?

I have a colour-diff script which contains (in Perl notation — I've translated it to Bash notation using ANSI C quoting as before):

reset=$'\e[0m' 
black=$'\e[30;1m'      # 30 = Black,   1 = bold
red=$'\e[31;1m'        # 31 = Red,     1 = bold
green=$'\e[32;1m'      # 32 = Green,   1 = bold
yellow=$'\e[33;1m'     # 33 = Yellow,  1 = bold
blue=$'\e[34;1m'       # 34 = Blue,    1 = bold
magenta=$'\e[35;1m'    # 35 = Magenta, 1 = bold
cyan=$'\e[36;1m'       # 36 = Cyan,    1 = bold
white=$'\e[37;1m'      # 37 = White,   1 = bold

With those variables around, you can create your function as you wish:

colorLine() {
    sed "/$1/s//$blue&$reset/“ file.txt
}

Where you set those variables depends on where you define your function. For myself, I'd probably make a script rather than a function, with full-scale argument parsing, and go from there. YMMV

Take a look at List of ANSI color escape sequences to get a more comprehensive list of colours (and other effects — including background and foreground colours) and the escape sequence used to generate it.

Upvotes: 4

Jotne
Jotne

Reputation: 41460

An awk version

black='\033[30;1m'
red='\033[31;1m'
green='\033[32;1m'
yellow='\033[33;1m'
blue='\033[34;1m'
magenta='\033[35;1m'
cyan='\033[36;1m'
white='\033[37;1m'

color=$cyan
colorLine() { awk -v s="$1" -v c=$color '$0~s {$0=c$0"\033[0m"}1' file.txt; }
colorLine "#.*"

You can add file as a variable as vell:

color=$cyan
file="file.txt"
colorLine() { awk -v s="$1" -v c=$color '$0~s {$0=c$0"\033[0m"}1' $file; }
colorLine "#.*"

In awk \e is printed as \033


A more dynamic version:

colorLine() { 
    temp=$2;
    col=${!temp};
    awk -v s="$1" -v c=$col '$0~s {$0=c$0"\033[0m"}1' $3; }
colorLine "#.*" green file.txt

Then you have colorLine pattern color file

Upvotes: 0

Léa Gris
Léa Gris

Reputation: 19675

I'd trick grep to do it for me this way:

function colorLine() {
    GREP_COLORS="mt=31" grep --color=always --context=$(wc -l <file.txt) --no-filename "$1" file.txt
}

Split-out of the trick:

  • GREP_COLORS="mt=31": SGR substring for matching non-empty text in any matching line. Here will generate \e[31m red before matched string, and reset to default color after matched string.

  • --color=always: always colorise even in non interactive shell

  • context=$(wc -l <file.txt): output as much context lines as number of lines in the file (so all lines).

  • --no-filename: do not print the file name

Upvotes: 0

Cyrus
Cyrus

Reputation: 88939

With GNU sed and Kubuntu 16.04.

foo="#.*"
sed 's/'"$foo"'/\x1b[31m&\x1b[0m/' file

Upvotes: 1

Related Questions