1

I'm trying to search files for specific text. Then print the line immediately prior to the line, and all subsequent lines that start with a specific character, particularly a 'space.'

Here's a sample of the file I'm trying to read:

interface vlan 22
 ip address 10.10.2.1 255.255.255.0
 ip helper-address 10.10.44.1
 ip helper-address 10.10.44.2
!
interface vlan 23
 ip address 10.10.23.1 255.255.255.0
 ip helper-address 10.10.44.1
 ip helper-address 10.10.44.2
!

When I see 'IP address' I want to print the line immediately prior, and then all the configuration items under that interface.

Currently, I'm reading from a directory of files and outputting specific information from the files. Here's the code:

for file in glob.glob('*.log'):
with open(file) as search:
    with open(queryoutput,"a") as foutput:
        for line in search:
            line = line.rstrip()
            if hostcheck in line:
                hostentry = line.split("hostname ")[1]
                foutput.write("Subnet information below is from " + hostentry + "\n")
            elif ipaddress in line:
                foutput.write("Local Device: " + hostentry + "\n")
                foutput.write("Remote " + line + "\n")

Not all network devices will state "interface" in the VLAN line, so that's why I don't want to search for that text, and there's not a guarantee an exclamation would be the last item, though it's highly probable. That's why I'm looking for a way to read the lines based on 'IP address' and spaces.

I'm still new to Python, and programming in general, but it looks like something like this might help. I'm just not fully understanding how that would work.

Any thoughts on how I can accomplish this? Also, I'm trying to use Python 3.x.

Mike Pennington
  • 41,899
  • 19
  • 136
  • 174
T.J.
  • 19
  • 5
  • You should try regex for such tasks where there is a pattern in strings. In short look for `re` module of Python. – Paandittya Feb 23 '19 at 00:37
  • Hadn't thought of this. The challenge here is there are no unique patterns that don't exist on other lines. For instance, there may be 200+ interfaces on a switch. I only want to see ones that actually have a specific 'pattern' or command set within them. Regex would only cover on a line-by-line basis wouldn't it? – T.J. Feb 23 '19 at 19:11
  • Line-by-line basis is not how it works. It operates on any string object. But anyway Cody's answer below is the best way forward. Btw ciscoconfparse uses regexes so there are patterns here for sure. – Paandittya Feb 24 '19 at 05:37

3 Answers3

2

It would probably be easier to utilize a Cisco config-parsing library rather than start from first principles.

For example, the ciscoconfparse module makes your problem as easy as the following, creating a dict with each interface mapped to a list of its configuration items. Searching for other object types would just be a matter of changing the argument to find_objects.

Assuming we're dealing with a file named test-config:

import pprint
from ciscoconfparse import CiscoConfParse

parse = CiscoConfParse("test-config", syntax="ios")

pprint.pprint({
    obj.text: [child.text.strip() for child in obj.children]
    for obj in parse.find_objects(r"interface")
})

Result:

{'interface vlan 22': ['ip address 10.10.2.1 255.255.255.0',
                       'ip helper-address 10.10.44.1',
                       'ip helper-address 10.10.44.2'],
 'interface vlan 23': ['ip address 10.10.23.1 255.255.255.0',
                       'ip helper-address 10.10.44.1',
                       'ip helper-address 10.10.44.2']}

Edit: Regarding your additional question, it would probably be wise to read the documentation and tutorial for the module which contains, among other things, examples of how to search for entries with specific children. To achieve what you're asking for, you could modify the above to use the find_objects_w_child() function:

pprint.pprint({
    obj.text: [child.text.strip() for child in obj.children]
    for obj in parse.find_objects_w_child(
        parentspec=r"^interf", childspec=r"ip .*address"
    )
})
cody
  • 11,045
  • 3
  • 21
  • 36
  • Dang... Didn't realize this module existed. I'll give it a shot a post back here on the results. Looks like it can run on more than just IOS so this may be exactly what's needed. Thanks! – T.J. Feb 23 '19 at 18:26
  • So I gave it a shot. Looks like this will help accomplish a couple of other goals as well - finding VLAN names and reviewing access port configurations. What I need to figure out to accomplish my initial goal from this post is to filter out only the interfaces. In most of the configs this script would run against there will be loads of configurations on each access port. For instance spanning tree or NAC configurations. If I run against a stack of five switches, I could get an output of 240 interface configurations in addition to the IP configurations. Thoughts? – T.J. Feb 23 '19 at 19:07
  • Maybe there's a way to say "If output of ciscoconfparse contains 'ip address' or 'ip helper-address' then print? – T.J. Feb 23 '19 at 19:16
  • Thanks. I'll do some more reading from the documentation. I didn't realize you could leverage a parent/child relationship with those commands. Looks like it won't take much to get that component working. – T.J. Feb 24 '19 at 22:20
0

Here's a sample from the ciscoconfparse recommended above. This actually is really useful. Just wondering if I can somehow exclude all the switch interfaces for devices.

     'interface FastEthernet0/7': ['spanning-tree portfast'],
 'interface FastEthernet0/8': ['switchport access vlan 300',
                               'switchport mode access',
                               'authentication event fail action next-method',
                               'authentication event server dead action reinitialize vlan 999',
                               'authentication host-mode multi-auth',
                               'authentication order dot1x mab',
                               'authentication priority dot1x mab',
                               'authentication port-control auto',
                               'mab eap',
                               'dot1x pae authenticator',
                               'spanning-tree portfast'],
 'interface FastEthernet0/9': ['switchport access vlan 300',
                               'switchport mode access',
                               'authentication event fail action next-method',
                               'authentication event server dead action reinitialize vlan 999',
                               'authentication host-mode multi-auth',
                               'authentication order dot1x mab',
                               'authentication priority dot1x mab',
                               'authentication port-control auto',
                               'mab eap',
                               'dot1x pae authenticator',
                               'spanning-tree portfast'],
 'interface GigabitEthernet0/1': [],
 'interface GigabitEthernet0/2': [],
 'interface Vlan1': ['no ip address', 'shutdown'],
 'interface Vlan300': ['ip address 172.22.0.1 255.255.255.0',
                       'ip helper-address 10.208.111.196',
                       'ip helper-address 10.208.111.195'],
 'interface Vlan310': ['ip address 172.31.200.1 255.255.255.0',
                       'ip access-group guest-restrictions in',
                       'ip helper-address 10.208.111.195',
                       'ip helper-address 10.208.111.196'],
 'interface Vlan500': ['ip address 172.19.0.2 255.255.255.248'],
T.J.
  • 19
  • 5
0

You can use TTP to parse above text, here is the code:

from ttp import ttp

data = """
interface vlan 22
 ip address 10.10.2.1 255.255.255.0
 ip helper-address 10.10.44.1
 ip helper-address 10.10.44.2
!
interface vlan 23
 ip address 10.10.23.1 255.255.255.0
 ip helper-address 10.10.44.1
 ip helper-address 10.10.44.2
!
interface Fast0/31
 switchport
!
interface vlan 77
 description Not In Use
!
"""

template = """
<group contains="ip">
interface {{ interface | ORPHRASE }}
 ip address {{ ip }} {{ mask }}
 ip helper-address {{ dhcp | to_list | joinmatches }}
</group>
"""

parser = ttp(data, template)
parser.parse()
print(parser.result(format="json")[0])

will print:

[
    [
        {
            "dhcp": [
                "10.10.44.1",
                "10.10.44.2"
            ],
            "interface": "vlan 22",
            "ip": "10.10.2.1",
            "mask": "255.255.255.0"
        },
        {
            "dhcp": [
                "10.10.44.1",
                "10.10.44.2"
            ],
            "interface": "vlan 23",
            "ip": "10.10.23.1",
            "mask": "255.255.255.0"
        }
    ]
]

TTP allows to filter results based on matches produced, in this particular case group "contains" function invalidates results for interfaces that does not have matches for "ip"

apraksim
  • 191
  • 4