0
INPUT | grep --color=always "$string\{1,1\}"

... alas, every instance of $string is highlighted. As I read the docs, the '{1,1}' should insure that only the first instance is highlighted. Or is there a better tool? I can't use sed

INPUT | sed -r "s|$string|${red}$string${nrm}|I"

( ${red} and ${nrm} are just color codes )

because the INPUT here is a list of files which might be found case insensitively and if I use the 'I' option with sed, it forces the case of the file list to change to the exact text of the input to the command -- that is, I want to search for files without case, but highlight insensitively whatever the case of the string within the file list might happen to be.

my_command *file_name*

... will use 'find' to look for the files case insensitively but in the listing I want to highlight:

file_name 
FILE_nAME 
file_NAME_this_file_has_a_long_file_name

... but only the first instance (a filename might contain repetitions of the input string). So in the last line above 'file_NAME' is highlighted, but 'file_name' is not.

grep has no problem with the case issue but sed does. OTOH, sed knows how to stop after one match and grep seems not to. Or is there a better way?

2 Answers 2

2

If your grep support -P for perl-like regexps, you could do:

grep --color=always -P "^.*?\K\Q$string\E"

That can only match once per line since we're using ^ to anchor the match at the start of the line. That's followed by .*? that matches any number of characters as few as possible, and $string within \Q/\E so the contents of the $string variable is matched literally even it happens to contain regexp operators (other than \E). \K specifies that only what follows is to be Kept, so here coloured.

Or you could use the real thing and not have to rely on any GNUism:

STRING=$string perl -ne 'print if s/\Q$ENV{STRING}\E/\e[31m$&\e[m/'

Which works even if $string contains \E.

1
  • More and more I'm beginning to think I'd better learn perl. I solved one problem simply by replacing sed with perl and I didn't even have to change the command tail at all -- perl was a drop-in replacement. Jan 20, 2023 at 17:51
1

I think you can use sed if you use pattern substitution (the match is case insensitive, but the replacement uses the captured pattern, not the searched one):

INPUT | sed -r "s|(${string})|${red}\\1${nrm}|I"

Also, as @don_crissti noticed, if you want to colorize the entire found pattern, you needn't even use a capture group, you can use the "whatever it was" & code which is probably also slightly faster:

INPUT | sed -r "s|${string}|${red}&${nrm}|I"

Using two strings instead of color codes it seems to work on my bash:

$ red="_RED_"
$ nrm="_NORM_"
$ string="amd"
$ echo -e "linux-5.4.0-amd-backfire.tar.bz2\\nThis is AMD Radeon\\nNames: Amdiranifani amd Jefri" \
  | sed -r "s|(${string})|${red}\\1${nrm}|I"

linux-5.4.0-_RED_amd_NORM_-backfire.tar.bz2
This is _RED_AMD_NORM_ Radeon
Names: _RED_Amd_NORM_iranifani amd Jefri

The second "amd" in the third line does not get replaced, and all other replacements keep their original case.

The grep syntax means that it will match the first continuous repetition of a pattern, so in "bababa" will match only the first "ba", but in "baba___ba" it will match the first and the third occurrence.

Apparently grep colors the whole match, so can't be used in this instance.

5
  • Bullseye! One question: why the double backslash: '\\1'? I used your sample above including my color variables but it seems fine with a single backslash. Jan 19, 2023 at 17:59
  • 1
    You actually don't need a capture group () and \\1 in this particular case, just use ${string} in the LHS and & in the RHS. For people who don't have access to gnu sed and its I flag: use [sS][tT][rR][iI][nN][gG] (the actual value not the var name) in the LHS. Jan 19, 2023 at 18:04
  • 1
    @RayAndrews the backslash is the escape character, so I need to escape it twice in strings. You're right that it will work anyway, because the parser is clever enough to surmise that "\1" is not an escape code since 1 needs no escaping, so echo "\1" and echo "\\1" yield the same result. But echo "\\n" and echo "\n" do not. I routinely make mistakes when entering backslashes :-D
    – LSerni
    Jan 19, 2023 at 18:05
  • @don_crissti True! Added to the answer, thanks.
    – LSerni
    Jan 19, 2023 at 18:08
  • Ah! I didn't understand Don's comment but now I do, thanks both. Jan 19, 2023 at 18:47

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .