grep multiline

I want to count the number of interfaces that have some specific configuration in my router. I want to use the most basic tools found in linux (so dont have to assume anything else is installed) and I want to use as less commands as possible.

So this is my config:

frr version 7.5
frr defaults traditional
hostname R2
log syslog informational
no ipv6 forwarding
service integrated-vtysh-config
!
interface ens6
 ip router isis ISIS 
 isis circuit-type level-2-only
 isis network point-to-point
!
interface lo1
 ip router isis ISIS 
 isis passive
!
interface ens7
 ip router isis ISIS 
 isis circuit-type level-2-only
 isis network point-to-point
!
interface lo2
 ip router isis ISIS 
 isis passive
!
mpls ldp
 router-id 172.20.15.2
 !
 address-family ipv4
  discovery transport-address 172.20.15.2
  !
  interface ens6
  !
  interface ens7
  !
 exit-address-family
 !
!
router isis ISIS 
 net 49.0001.1720.2001.5002.00
!
line vty
!

And I want to count the number of interfaces that have “isis network point-to-point” regardless of any other config.

In this example, we have just two interfaces.

interface ens6
 ip router isis ISIS 
 isis circuit-type level-2-only
 isis network point-to-point

interface ens7
 ip router isis ISIS 
 isis circuit-type level-2-only
 isis network point-to-point

The pseudo-pattern should be something like:

^interface ens.*point-to-point$

So something that starts with “interface ens”, it can have anything after that and then it ends with “point-to-point”

Ideally I want to use just “grep” and it is a standard and common tool

But grep mainly works in one line each time. And my pattern covers multiple lines.

So I searched for some help and found this that uses “perl compatible regular expressions” (PCRE). I have no idea about perl but let’s give it a go:

$ grep -Pz '(?s)interface ens.*point-to-point\n' r5.txt
frr version 7.5
frr defaults traditional
hostname R2
log syslog informational
no ipv6 forwarding
service integrated-vtysh-config
!
interface ens6
 ip router isis ISIS 
 isis circuit-type level-2-only
 isis network point-to-point
!
interface lo1
 ip router isis ISIS 
 isis passive
!
interface ens7
 ip router isis ISIS 
 isis circuit-type level-2-only
 isis network point-to-point
!
interface lo2
 ip router isis ISIS 
 isis passive
!
mpls ldp
 router-id 172.20.15.2
 !
 address-family ipv4
  discovery transport-address 172.20.15.2
  !
  interface ens6
  !
  interface ens7
  !
 exit-address-family
 !
!
router isis ISIS 
 net 49.0001.1720.2001.5002.00
!
line vty
!

Let’s explain the parameters provided to grep so far:

  • -P: Use perl compatible regular expressions (PCRE).
  • -z: Treat the input as a set of lines, each terminated by a zero byte instead of a newline. i.e. grep treats the input as a one big line.
  • (?s): activate PCRE_DOTALL, which means that ‘.’ matches any character or newline.

But if I count, we dont have the expected answer of 2:

$ grep -Pzc '(?s)interface ens.*point-to-point\n' r5.txt
1

The “z” parameter is treating the file as a single line so for grep, there is one match only. The initial command shows in bold just one block.

We notice that the pattern is matching “interface lo1” and that is not what we want, it should be ignored.

So our pattern should match the smallest string. So we want a non-greedy matching regex. So searching again, found this. It seems for Perl regex, we need to use ? after *

$ grep -Pz '(?s)interface ens.*?point-to-point\n' r5.txt
frr version 7.5
frr defaults traditiona
hostname R2
log syslog informational
no ipv6 forwarding
service integrated-vtysh-config
!
interface ens6
 ip router isis ISIS 
 isis circuit-type level-2-only
 isis network point-to-point
!
interface lo1
 ip router isis ISIS 
 isis passive
!
interface ens7
 ip router isis ISIS 
 isis circuit-type level-2-only
 isis network point-to-point
!
interface lo2
 ip router isis ISIS 
 isis passive
!
mpls ldp
 router-id 172.20.15.2
 !
 address-family ipv4
  discovery transport-address 172.20.15.2
  !
  interface ens6
  !
  interface ens7
  !
 exit-address-family
 !
!
router isis ISIS 
 net 49.0001.1720.2001.5002.00
!
line vty
!

So now, we can see two blocks highlighted. So now let’s print only the matched strings using -o:

$ grep -Pzo '(?s)interface ens.*?point-to-point\n' r5.txt
interface ens6
 ip router isis ISIS 
 isis circuit-type level-2-only
 isis network point-to-point
interface ens7
 ip router isis ISIS 
 isis circuit-type level-2-only
 isis network point-to-point

So this looks correct but still counting (-c) doesnt work properly because -z is treating the entry as one big line.

I haven’t been able to find the solution with just one command so at the end, I have to pipe another grep. The initial grep matches the pattern, so the second one should just count a specific pattern like “point”. It should be that simple:

$ grep -Pzo '(?s)interface ens.*?point-to-point\n' r5.txt | grep point
grep: (standard input): binary file matches

Weird, I thought this was pure text but seems the ouput of the first grep has some binary data:

$ grep -Pzo '(?s)interface ens.*?point-to-point\n' r5.txt > r55.txt
$ vim r55.txt
interface ens6
 ip router isis ISIS
 isis circuit-type level-2-only
 isis network point-to-point
^@interface ens7
 ip router isis ISIS 
 isis circuit-type level-2-only
 isis network point-to-point
^@

But we can tell grep to read binary data too using -a as per this blog and then count.

$ grep -Pzo '(?s)interface ens.*?point-to-point\n' r5.txt | grep -a point
 isis network point-to-point
 isis network point-to-point
$ grep -Pzo '(?s)interface ens.*?point-to-point\n' r5.txt | grep -ac point
2

Funny enough, if I just want to count, I dont need -a:

$ grep -Pzo '(?s)interface ens.*?point-to-point\n' r5.txt | grep -c point
2

So not sure if this is the best solution but it took me a bit to find it. It seems to work:

grep -Pzo ‘(?s)interface ens.*?point-to-point\n’ r5.txt | grep -ac point