148

Say I have the following file:

$ cat test

test line 1
test line 2
line without the search word
another line without it
test line 3 with two test words
test line 4

By default, grep returns each line that contains the search term:

$ grep test test

test line 1
test line 2
test line 3 with two test words
test line 4

Passing the --color parameter to grep will make it highlight the portion of the line that matches the search expression, but it still only returns lines that contain the expression. Is there a way to get grep to output every line in the source file, but highlight the matches?

My current terrible hack to accomplish this (at least on files that don't have 10000+ consecutive lines with no matches) is:

$ grep -B 9999 -A 9999 test test

Screenshot of the two commands

If grep can't accomplish this, is there another command-line tool that offers the same functionality? I've fiddled with ack, but it doesn't seem to have an option for it either.

4
  • 1
    Possible cross site duplicate of: stackoverflow.com/questions/981601/… The top answer is the same on both. Feb 16, 2014 at 14:29
  • 3
    You can use -C 9999 in place of -A 9999 -B 9999.I always do: grep -C 9999 pattern file
    – neo
    Aug 28, 2016 at 12:38
  • Try sed. For example, here's how to highlight + return exit code: askubuntu.com/a/1200851/670392
    – Noam Manos
    Jan 5, 2020 at 16:10
  • -C9999 (or -C99 for a short input) might actually be high up on the list of elegance/availability, and gives the right return value if it finds a match. The only downside is it won't print anything if it doesn't find one.
    – mwfearnley
    May 18 at 9:09

13 Answers 13

215
grep --color -E "test|$" yourfile

What we're doing here is matching against the $ pattern and the test pattern, obviously $ doesn't have anything to colourize so only the test pattern gets color. The -E just turns on extended regex matching.

You can create a function out of it easily like this:

highlight () { grep --color -E "$1|$" "${@:1}" ; }
14
  • 15
    That's freaking awesome!
    – gvkv
    Aug 12, 2010 at 3:09
  • 3
    And as a function: highlight () { grep --color -E "$1|$" $2 ; }. Usage: highlight test yourfile Oct 14, 2010 at 3:48
  • 6
    Better could be: highlight () { grep --color -E "$1|$" "$@" } which allows files with whitespace in their names and multiple files. Jul 24, 2011 at 5:34
  • 15
    @MikeDeSimone - But that will also have "$1" in the files. Use highlight () { grep --color -E "$1|$" "${@:1}" }
    – Chris Down
    Nov 17, 2011 at 11:48
  • 3
    I came up with this ${@:2} with bash because you dont want the first argument to be repeated in the list files to check. highlight() { grep --color -E -- "$1|\$" "${@:2}"; }. Note the -- also to signigy that arguments with dash are done, so if you search a pattern line -something its still working.
    – Lynch
    Mar 13, 2016 at 5:08
42
ack --passthru --color string file

for Ubuntu and Debian, use ack-grep instead of ack

ack-grep --passthru --color string file
4
  • 2
    Oh, that's quite obviously in the man page; I'm blind. Thanks Aug 17, 2010 at 20:03
  • the pattern for grap may be specified explicitly with --regexp string; the ack/ack-grep equivalent is --match string
    – Rhubbarb
    Jan 8, 2015 at 17:46
  • To simulate ack --passthru with perl : grep_passthrough () { ! perl -ne "print; \$e ||= /$@/; END{{exit \$e}}"; } Mar 4, 2016 at 17:58
  • ack --passthru also works on streams! For example, ifconfig | ack --passthru inet
    – cydonian
    Jun 16, 2016 at 0:10
18

Another way to do this properly and portably with grep (besides using two regexes with alternation as in the accepted answer) is via the null pattern (and respectively null string).
It should work equally well with both -E and -F switches since, per the standard:

-E
    Match using extended regular expressions. 
    [...] A null ERE shall match every line.

and

-F
    Match using fixed strings.
    [...] A null string shall match every line.

So it's simply a matter of running

grep -E -e '' -e 'pattern' infile

and respectively

grep -F -e '' -e 'string' infile
5
  • 1
    Or just grep -F -e '' -e 'string'. Jan 26, 2017 at 23:14
  • It works for me with GNU grep 2.25, busybox grep and the heirloom toolchest one. Jan 26, 2017 at 23:25
  • OK, my bad, seq 3 | grep -F -e '' -e 2 does output only 2 in 2.27 (and 2.26, but not 2.25, and not in the git head). Only with -F (not without or with -E). IOW, only 2.26 and 2.27 seem to have the bug and only with -F. Jan 26, 2017 at 23:56
  • Note that seq 3 | grep -F -e '' -e '' -e 2 does seem to work as well with 2.27 Jan 27, 2017 at 0:03
  • 1
    The method given in this answer is simple, correct, and seems quite fast. As you say, it works automatically with -F, alleviating the need to worry about what characters appear in string. You might want to mention --color; as this answer floats up on the page (when viewed by votes), more people may end up reading it before the other answers. Jan 27, 2017 at 5:12
12

I have the following function that I use for such things:

highlight () {
    perl -pe "s/$1/\e[1;31;43m$&\e[0m/g"
}

Internally it looks kind of ugly, it's nice and easy to use, like so:

cat some_file.txt | highlight some_word

or, for a slightly more real-world example:

tail -f console.log | highlight ERROR

You can change the colors to anything you like (which might be hard with grep--I'm not sure) by changing the 1 and 31 and 43 (after \e[) to different values. The codes to use are all over the place, but here's a quick intro: the 1 bolds the text, the 31 makes it red, and the 43 gives a yellow background. 32 or 33 would be different colors, and 44 or 45 would be different backgrounds: you get the idea. You can even make it blink (with a 5) if you're so inclined.

This doesn't use any special Perl modules, and Perl is nearly ubiquitous, so I would expect it to work just about anywhere. The grep solution is very clever, but the --color switch on grep is not available everywhere. For instance, I just tried this solution on a Solaris box running bash, and another running ksh, and my local Mac OS X machine running zsh. All worked just fine. Solaris choked on the grep --color solution, however.

Also, ack is awesome, and I recommend it to anyone who hasn't yet discovered it, but I've had some issues installing it on a few of the many servers I work on. (I forget why: I think related to Perl modules it required.)

Since I don't think I've ever worked on a Unix box that didn't have Perl installed (with the exception of embedded-type systems, Linksys routers, and such) I'd say this is pretty much a universally useable solution.

2
  • I like this solution because it does just what you want rather than bend and existing tool like grep which is more indirect. It also gives you more flexibility should you want to change the behavior. (Although just a note you can change the colors with grep with the env var GREP_COLORS which uses the same SGR codes).
    – wardw
    Dec 17, 2020 at 16:16
  • Also just a note that the colors are reset with \e[0m after the match and so this will also clear any existing colorization in the input stream. That's also true of grep, however. If you need to handle input that's already colorized, you might consider wrapping your match text with the terminal controls tput smso and tput rmso instead.
    – wardw
    Dec 17, 2020 at 16:20
6

ripgrep

Use ripgrep with its --passthru parameter:

rg --passthru pattern file.txt

It's one of the fastest grepping tools, since it's built on top of Rust's regex engine which uses finite automata, SIMD and aggressive literal optimizations to make searching very fast.

--passthru - Print both matching and non-matching lines.

Another way to achieve a similar effect is by modifying your pattern to match the empty string. For example, if you are searching using rg foo then using rg "^|foo" instead will emit every line in every file searched, but only occurrences of foo will be highlighted. This flag enables the same behavior without needing to modify the pattern.

1
  • 1
    This is the most helpful answer, especially since the exit code isn't zero when the pattern wasn't matched. For example: grep --color -E "test|$" yourfile just highlights all occurrences of "test" in the file and even when there are no matches, it still returns 0 which is counter-intuitive. However: rg --passthru test yourfile highlights the occurrences but returns a non-zero exit code when the pattern wasn't matched.
    – Miraclx
    Feb 11 at 22:26
3

Instead of using Grep, you can use Less:

less file

Search like this: /pattern Enter

This will:

  1. scroll to first matching line
  2. output every line starting from there on
  3. highlight all matches

To scroll to next matching line: n

To scroll to previous matching line: N

To toggle highlighting: Esc u

Also you can change the highlighting color if you like.

2

You can try:

perl -MTERM::ANSIColor -nle '/pattern/ ? print colored($_, 'color') : print' test

Not very portable however, and even if Perl is installed, you may need to download another module. In addition it will color the entire line, not just the search word.

2

There's a much easier way to do this for GNU grep but I don't think it's portable (i.e., BSD grep):

In a pipe:

cat <file> | grep --color=always -z <query>

On a file:

grep --color=always -z <query> <file>

Credit goes to Cyrus's answer here.

2
  • 1
    This isn’t as good an answer as you (and Cyrus) think.  It has the effect of treating the entire file as a single line.  Therefore, (1) if the file is very large, there may be a possibility of running out of memory, and (2) if the file doesn’t contain the pattern at all, then nothing will be output. And you’re right; it isn’t universally available. (But then again, --color isn’t standard either.) Feb 6, 2017 at 4:43
  • What version of grep is this supported on? On grep 2.5.4, -z doesn't seem available...
    – Alex
    Jun 29, 2017 at 22:29
2

None of the answers given so far do provide a portable solution.

Here is a portable1 shell function I already posted in a closed as duplicate question that doesn't require non standard tools or non standard extensions provided with perl, ack, ggrep, gsed, bash and the likes but only needs a POSIX shell and the POSIX mandatory utilities sed and printf:

grepc()
{
  pattern=$1
  shift
  esc=$(printf "\033")
  sed 's"'"$pattern"'"'$esc'[32m&'$esc'[0m"g' "$@"
}

You can use it that way:

grepc string_to_search [file ...]

The highlight color can be adjusted by using one of these codes in the sed command argument (32m being green here):

30m black
31m red
33m yellow
34m blue
35m magenta
36m cyan
37m white
7m reverse video

1 As long as your terminal supports ANSI colors escape sequences.

2

If you're interested in the return value of grep, then GNU sed is also the way:

sed '/pattern/h; ${p;x;/./Q0;Q1}'

You can add to the script modifications to the line, e.g.:

sed '/pattern/h; ${p;x;/./Q0;Q1}; s/pattern/<\0>/g'

This checks that pattern appears on the current line, and stores it in the hold buffer if it is. At the end of the file ($), the last line is printed (p), then the hold space and pattern space are swapped (x). Next, the pattern space is tested for emptiness (/./), and if it is nonempty, exit with code 0 (Q0) otherwise with code 1 (Q1).

An alternative is /pattern/{:a $q1; n; ba}, which loops after detecting pattern, but changing the current line can get tricky with this. This works by:

  • /pattern/: the following clause will only be executed when the pattern is detected on a line.
  • :a … ba: this is defining a label a and branching to it; as is, this is an infinite loop.
  • n: prints the current line and loads the next one.
  • $q1: when reaching the end of file ($), exit with code 1.

By default, sed will exit with code 0, so with this expression, if the pattern is detected, the rest of the file is dumped and an exit code of 1 is produced.

3
  • Thanks for this answer Michaël, and for explaining the script! Can you also explain how the alternative script works?
    – mwfearnley
    May 18 at 9:06
  • 1
    Sure thing @mwfearnley ! See edited answer.
    – Michaël
    May 18 at 15:34
  • Nice - thanks! I wish I could upvote a second time…
    – mwfearnley
    May 18 at 15:43
0

A sed version, works on both bash and ash.

#highlight
hsed(){
    local pattern="$1"
    shift
    local r=`echo -e '\e'[31m`
    local c=`echo -e '\e'[0m`
    sed "s:${pattern//:/\:}:$r\0$c:g" "$@"
}
0

OP asked for grep, and that is what I RECOMMEND; but after trying hard to solve a problem with sed, for the record, here is a simple solution with it:

sed $'s/main/\E[31m&\E[0m/g' testt.c

or

cat testt.c | sed $'s/main/\E[31m&\E[0m/g'

Will paint main in red.

  • \E[31m : red color start sequence
  • \E[0m : finished color mark
  • & : the matched pattern
  • /g : all words in a line, not just the first
  • $'string' is bash strings with escaped characters interpreted

Regarding grep, it also works using ^ (begin of line) instead of $ (end of line). Example:

egrep "^|main" testt.c

And just to show this crazy alias that I DO NOT RECOMMEND, you can even let the open quotes:

alias h='egrep -e"^|'
h main" testt.c
cat testt.c | h main"

all work! :) Don't worry if you forget to close the quote, bash will remember you with a "continuing line character".

0

Similar to the perl answer, but I use it with multiple expressions separated by |:

tail -F somefile.log | perl -pe 's/\bfirstword\b/\e[1;31m$&\e[0m/g|s/\bsecondword\b/\e[1;32m$&\e[0m/g'

You and add more words by append multiple | (i.e., |s/\bmorewords\b/\e[1;32m$&\e[0m/g) and on What color codes can I use in my bash PS1 prompt? you can see more bash the colors to use.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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