Context matching

Sometimes you want not just the matching lines, but the lines relative to the matches as well. For example, it could be to see the comments at start of a function block that was matched while searching a program file. Or, it could be to see extended information from a log file while searching for a particular error message. GNU grep has three options to display lines after, before or both combined relative to matching lines.

info Files used in examples are available chapter wise from learn_gnugrep_ripgrep repo. The directory for this chapter is context_matching.

The sample input file for this chapter is shown below:

$ cat context.txt
wheat
    roti
    bread

blue
    toy
    flower
    sand stone
light blue
    flower
    sky
    water
dark red
    ruby
    blood
    evening sky
    rose

language
    english
    hindi
    spanish
    tamil

programming language
    python
    kotlin
    ruby

-A

Additionally display lines after the matching lines. The number of lines required has to be specified after -A

info If there are multiple matches, by default grep adds a separator line -- between the results.

$ # show lines containing 'blue' and two lines after such lines
$ grep -A2 'blue' context.txt
blue
    toy
    flower
--
light blue
    flower
    sky

$ # use other options and regular expressions to refine the search
$ grep -x -A2 'blue' context.txt
blue
    toy
    flower

-B

Additionally display lines before the matching lines.

$ grep -B2 'bread' context.txt
wheat
    roti
    bread

$ grep -B3 'ruby' context.txt
    sky
    water
dark red
    ruby
--
programming language
    python
    kotlin
    ruby

-C

This option can be used instead of specifying both -A and -B if the count of lines required is the same. You can also use -N instead of -CN.

$ # same as: grep -A1 -B1 'sky' context.txt
$ # can also use: grep -1 'sky' context.txt
$ grep -C1 'sky' context.txt
    flower
    sky
    water
--
    blood
    evening sky
    rose

$ grep -A1 -B2 'sky' context.txt
light blue
    flower
    sky
    water
--
    ruby
    blood
    evening sky
    rose

info No error or warning if the count goes beyond lines available for any of these three options.

$ grep -C2 'kotlin' context.txt
programming language
    python
    kotlin
    ruby

Contiguous matches

The separator -- won't be added if two or more groups of matching lines have overlapping lines or are next to each other in input file.

$ # -n option used only for illustration purposes
$ # prefix is : for matching lines and - for relative lines
$ # group 6-8 and group 9-11 are next to each other here
$ grep -n -C1 'flower' context.txt
6-    toy
7:    flower
8-    sand stone
9-light blue
10:    flower
11-    sky

$ # example for overlapping case (line number 9)
$ # last line of 1st group overlaps with matching line of 2nd group
$ grep -n -A4 'blue' context.txt
5:blue
6-    toy
7-    flower
8-    sand stone
9:light blue
10-    flower
11-    sky
12-    water
13-dark red

Customizing separators

Use --group-separator to change the default separator -- to something else.

$ seq 29 | grep --group-separator='*****' -A1 '3'
3
4
*****
13
14
*****
23
24

$ # only matching line is shown if count 0 is used with -A/-B/-C
$ grep -A0 --group-separator='*-----------*-----------*' 'in' context.txt
    evening sky
*-----------*-----------*
    hindi
*-----------*-----------*
programming language
*-----------*-----------*
    kotlin

Use --no-group-separator option if the separator line is a hindrance, for example feeding the output of grep to another program.

$ seq 29 | grep --no-group-separator -A1 '3'
3
4
13
14
23
24

$ # sum of values based on grep output
$ seq 29 | grep --no-group-separator -A1 '3' | datamash sum 1
81

Summary

Context matching got its own chapter mainly due to corner cases and customization options (on Ubuntu, man grep doesn't even list them).

Exercises

a) For this question, create exercises/context_matching directory and then save this file from learn_gnugrep_ripgrep repo as palindrome.py. For this input file, display all lines matching raise and one line before it.

$ # assumes 'exercises/context_matching' as CWD
$ grep ##### add your solution here
    if re.search(r'[^a-zA-Z]', ip_str):
        raise ValueError("Characters other than alphabets and punctuations")
    elif len(ip_str) < 3:
        raise ValueError("Less than 3 alphabets")

b) Input has group of lines with single empty line in between. Change it to double empty lines between groups.

$ lines='rat\ndog\nbat\n\n42\n3.14\n\nhi there\nhave a nice day'
$ printf '%b' "$lines" | grep ##### add your solution here
rat
dog
bat


42
3.14


hi there
have a nice day