Finditer function#

Function finditer:

  • is used to search for all non-overlapping matches in string

  • returns an iterator with Match objects

  • finditer returns iterator even if no match is found

Function finditer is well suited to handle those commands whose output is displayed by columns. For example: ‘sh ip int br’, ‘sh mac address-table’, etc. In this case it can be applied to the entire output of command.

Example of ‘sh ip int br’ output:

In [8]: sh_ip_int_br = '''
   ...: R1#show ip interface brief
   ...: Interface             IP-Address      OK? Method Status           Protocol
   ...: FastEthernet0/0       15.0.15.1       YES manual up               up
   ...: FastEthernet0/1       10.0.12.1       YES manual up               up
   ...: FastEthernet0/2       10.0.13.1       YES manual up               up
   ...: FastEthernet0/3       unassigned      YES unset  up               up
   ...: Loopback0             10.1.1.1        YES manual up               up
   ...: Loopback100           100.0.0.1       YES manual up               up
   ...: '''

regex for output processing:

In [9]: result = re.finditer(r'(\S+) +'
   ...:                      r'([\d.]+) +'
   ...:                      r'\w+ +\w+ +'
   ...:                      r'(up|down|administratively down) +'
   ...:                      r'(up|down)',
   ...:                      sh_ip_int_br)
   ...:

result variable contains an iterator:

In [12]: result
Out[12]: <callable_iterator at 0xb583f46c>

Iterator contains Match objects:

In [16]: groups = []

In [18]: for match in result:
    ...:     print(match)
    ...:     groups.append(match.groups())
    ...:
<_sre.SRE_Match object; span=(103, 171), match='FastEthernet0/0       15.0.15.1       YES manual >
<_sre.SRE_Match object; span=(172, 240), match='FastEthernet0/1       10.0.12.1       YES manual >
<_sre.SRE_Match object; span=(241, 309), match='FastEthernet0/2       10.0.13.1       YES manual >
<_sre.SRE_Match object; span=(379, 447), match='Loopback0             10.1.1.1        YES manual >
<_sre.SRE_Match object; span=(448, 516), match='Loopback100           100.0.0.1       YES manual >'

Now in groups list there are tuples with strings that fallen into groups:

In [19]: groups
Out[19]:
[('FastEthernet0/0', '15.0.15.1', 'up', 'up'),
 ('FastEthernet0/1', '10.0.12.1', 'up', 'up'),
 ('FastEthernet0/2', '10.0.13.1', 'up', 'up'),
 ('Loopback0', '10.1.1.1', 'up', 'up'),
 ('Loopback100', '100.0.0.1', 'up', 'up')]

A similar result can be obtained by a list comprehension:

In [20]: regex = r'(\S+) +([\d.]+) +\w+ +\w+ +(up|down|administratively down) +(up|down)'

In [21]: result = [match.groups() for match in re.finditer(regex, sh_ip_int_br)]

In [22]: result
Out[22]:
[('FastEthernet0/0', '15.0.15.1', 'up', 'up'),
 ('FastEthernet0/1', '10.0.12.1', 'up', 'up'),
 ('FastEthernet0/2', '10.0.13.1', 'up', 'up'),
 ('Loopback0', '10.1.1.1', 'up', 'up'),
 ('Loopback100', '100.0.0.1', 'up', 'up')]

Now we will analyze the same log file that was used in search and match subsections.

In this case it is possible to pass the entire contents of file (parse_log_finditer.py):

import re

regex = (r'Host \S+ '
         r'in vlan (\d+) '
         r'is flapping between port '
         r'(\S+) and port (\S+)')

ports = set()

with open('log.txt') as f:
    for m in re.finditer(regex, f.read()):
        vlan = m.group(1)
        ports.add(m.group(2))
        ports.add(m.group(3))

print('Loop between ports {} in VLAN {}'.format(', '.join(ports), vlan))

Warning

In real life, a log file can be very large. In that case, it’s better to process it line by line.

Output will be the same:

$ python parse_log_finditer.py
Loop between ports Gi0/19, Gi0/24, Gi0/16 в VLAN 10

Processing of ‘show cdp neighbors detail’ output#

finditer can handle output of ‘sh cdp neighbors detail’ as well as in re.search subsection.

The script is almost identical to version with re.search (parse_sh_cdp_neighbors_detail_finditer.py file):

import re
from pprint import pprint


def parse_cdp(filename):
    regex = (r'Device ID: (?P<device>\S+)'
             r'|IP address: (?P<ip>\S+)'
             r'|Platform: (?P<platform>\S+ \S+),'
             r'|Cisco IOS Software, (?P<ios>.+), RELEASE')

    result = {}

    with open(filename) as f:
        match_iter = re.finditer(regex, f.read())
        for match in match_iter:
            if match.lastgroup == 'device':
                device = match.group(match.lastgroup)
                result[device] = {}
            elif device:
                result[device][match.lastgroup] = match.group(match.lastgroup)

    return result

pprint(parse_cdp('sh_cdp_neighbors_sw1.txt'))

Now matches are searched throughout the file, not in every line separately:

with open(filename) as f:
    match_iter = re.finditer(regex, f.read())

Then matches go through the loop:

with open(filename) as f:
    match_iter = re.finditer(regex, f.read())
    for match in match_iter:

The rest is the same.

The result will be:

$ python parse_sh_cdp_neighbors_detail_finditer.py
{'R1': {'ios': '3800 Software (C3825-ADVENTERPRISEK9-M), Version 12.4(24)T1',
        'ip': '10.1.1.1',
        'platform': 'Cisco 3825'},
 'R2': {'ios': '2900 Software (C3825-ADVENTERPRISEK9-M), Version 15.2(2)T1',
        'ip': '10.2.2.2',
        'platform': 'Cisco 2911'},
 'SW2': {'ios': 'C2960 Software (C2960-LANBASEK9-M), Version 12.2(55)SE9',
         'ip': '10.1.1.2',
         'platform': 'cisco WS-C2960-8TC-L'}}

Although the result is similar, finditer has more features, as you can specify not only what should be in searched string but also in strings around it. For example, you can specify exactly which IP address to take:

Device ID: SW2
Entry address(es):
  IP address: 10.1.1.2
Platform: cisco WS-C2960-8TC-L,  Capabilities: Switch IGMP

...

Native VLAN: 1
Duplex: full
Management address(es):
  IP address: 10.1.1.2

If you want to take the first IP address you can supplement a regex like this:

regex = (r'Device ID: (?P<device>\S+)'
         r'|Entry address.*\n +IP address: (?P<ip>\S+)'
         r'|Platform: (?P<platform>\S+ \S+),'
         r'|Cisco IOS Software, (?P<ios>.+), RELEASE')