Python - Read Specific Lines of Text - python

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.

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"
)
})

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'],

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"

Related

Python telnet read a long output issue

Here is my telnet script as:
import sys
import getpass
import telnetlib
import time
HOST = "192.168.182.129"
user = input("Enter your remote telnet account: ")
password = getpass.getpass()
tn = telnetlib.Telnet(HOST)
tn.read_until(b"Username: ")
tn.write(user.encode('ascii') + b"\n")
if password:
tn.read_until(b"Password: ")
tn.write(password.encode('ascii') + b"\n")
tn.write(b"show version\n")
time.sleep(5)
tn.write(b"config t\n")
time.sleep(2)
tn.write(b"interface loopback 1\n")
time.sleep(2)
tn.write(b"ip address 8.8.8.8 255.255.255.0\n")
tn.write(b"end\n")
tn.write(b"exit\n")
print(tn.read_all().decode('ascii'))
Here, If I am using some long output command as "Show Version" then this script is not working as wanted and it will show only few lines of output and also cut the next command as:
Enter your remote telnet account: deepak
Password:
R1#show version
Cisco IOS Software, 7200 Software (C7200-ADVIPSERVICESK9-M), Version 15.2(4)S5, RELEASE SOFTWARE (fc1)
Technical Support: http://www.cisco.com/techsupport
Copyright (c) 1986-2014 by Cisco Systems, Inc.
Compiled Thu 20-Feb-14 06:51 by prod_rel_team
ROM: ROMMON Emulation Microcode
BOOTLDR: 7200 Software (C7200-ADVIPSERVICESK9-M), Version 15.2(4)S5, RELEASE SOFTWARE (fc1)
R1 uptime is 2 hours, 16 minutes
System returned to ROM by unknown reload cause - suspect boot_data[BOOT_COUNT] 0x0, BOOT_COUNT 0, BOOTDATA 19
System image file is "tftp://255.255.255.255/unknown"
Last reload reason: Unknown reason
This product contains cryptographic features and is subject to United
States and local country laws governing import, export, transfer and
use. Delivery of Cisco cryptographic products does not imply
third-party authority to import, export, distribute or use encryption.
Importers, exporters, **distributors and users are responsible for**
**R1#onfig t**
% Invalid input detected at '^' marker.
R1#interface loopback 1
% Invalid input detected at '^' marker.
R1#ip address 8.8.8.8 255.255.255.0
% Invalid input detected at '^' marker.
R1#end
Looking your help for the same.
Instead of:
tn.write(b"config t\n")
Try to use:
tn.write("config t\n")
A better way to compact you script will be to store the commands to config into a list like this way:
commands =['conf t', 'interface loopback 1', 'ip address 8.8.8.8 255.255.255.0']
for command in commands:
tn.write(command+'\n')
time.sleep(1)
At the end of your code you change some thing like this, but first be sure about your Router :
Be sure that in your Router you configured :
aaa new-model ( with : aaa authentication login default local )
Also in line vty 0 15 : transport input all, login authentication default
# if you configured enable password you should use this section :
tn.write(b"enable\n")
tn.write(b"cisco\n")
# Code for test :
tn.write(b"des TEST\n")
tn.write(b"end\n")
tn.write(b"exit\n")
# **End section should be exactly like this line**
print(tn.read_all())

How to replace words/digits in a file

I'm trying to replace the global IP inside a file with a private one and create a mapping of them, so i can revert it back even if part of the new string is different.
I'm stuck at the point of replace the global IP with the bogus one and write it to a file.
Starting file example:
ip route 192.168.1.0 255.255.0.0 10.10.10.2
ip route 192.168.1.0 255.255.0.0 1.1.1.2
ip route 1.1.1.1 255.255.0.0 1.1.1.3
interface FastEthernet1
ip address 1.1.1.1
duplex auto
speed auto
Wanted end result, some wording may change before revert back:
ip route ipv4 192.168.1.0 255.255.0.0 10.10.10.2
ip route ipv4 192.168.1.0 255.255.0.0 10.1.1.11
ip route ipv4 10.1.1.10 255.255.0.0 10.1.1.12
interface FastEthernet1
ip address 10.1.1.10
duplex auto
speed auto
The mapping I though is a dictionary like this:
mapping = {
'1.1.1.2': "10.1.1.10",
'1.1.1.1': "10.1.1.10",
'1.1.1.3': "10.1.1.30
}
I came out with this script until now, but it not doing what I want:
import re
import ipaddress
def load_file(file) -> str:
with open(file, 'r') as f:
return f.read()
def find_ips(config) -> set:
ip_regex = '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'
match = set(re.findall(ip_regex, config))
return match
def mapping_ip(ips) -> dict:
counter = 0
ip_table = {}
for ip in ips:
ip4_type = ipaddress.IPv4Address(ip)
if ip4_type.is_global:
counter += 1
private = ipaddress.IPv4Address('10.1.1.10') + counter
ip_table.update({
ip: str(private),
})
return ip_table
def replace(mapping, s_file, d_file):
with open(s_file, 'r') as reader, open(d_file, 'w') as writer:
for line in reader:
for orig, temp in mapping.items():
if orig in line:
x = line.replace(orig, temp)
writer.write(x)
Any suggestion on how should I do the replace funcion?
Only the IP can be change, the rest of the string need to stay as it's(revert back process).
You can simply use string replace on the lines of your source file:
Create source file:
t = """ip route 192.168.1.0 255.255.0.0 10.10.10.2
ip route 192.168.1.0 255.255.0.0 1.1.1.2
ip route 1.1.1.1 255.255.0.0 1.1.1.3
interface FastEthernet1
ip address 1.1.1.1
duplex auto
speed auto"""
with open("t.txt","w") as f:
f.write(t)
Replace stuff and write to "mod.txt":
mapping = {
'1.1.1.2': "10.1.1.10",
'1.1.1.1': "10.1.1.10",
'1.1.1.3': "10.1.1.30"
}
with open("mod.txt","w") as m, open("t.txt") as data:
for line in data:
for key,replacewith in mapping.items():
line = line.replace(key,replacewith)
m.write(line)
with open("mod.txt") as f:
print(f.read())
Output:
ip route 192.168.1.0 255.255.0.0 10.10.10.2
ip route 192.168.1.0 255.255.0.0 10.1.1.10
ip route 10.1.1.10 255.255.0.0 10.1.1.30
interface FastEthernet1
ip address 10.1.1.10
duplex auto
speed auto
This will try to replace each line m times (m == len(mapping)) and is not very speedy due to creating lots of intermediate strings (if something got replaced) - it is more a hacky solution to your problem.
You could harness re.sub in this case, following way:
import re
txt = 'ip route 192.168.1.0 255.255.0.0 10.10.10.2\nip route 192.168.1.0 255.255.0.0 1.1.1.2\nip route 1.1.1.1 255.255.0.0 1.1.1.3\ninterface FastEthernet1\nip address 1.1.1.1\nduplex auto\nspeed auto'
out = re.sub(r'1\.1\.1\.([1-3])','10.1.1.\g<1>0',txt)
print(out)
Output:
ip route 192.168.1.0 255.255.0.0 10.10.10.2
ip route 192.168.1.0 255.255.0.0 10.1.1.20
ip route 10.1.1.10 255.255.0.0 10.1.1.30
interface FastEthernet1
ip address 10.1.1.10
duplex auto
speed auto
For simplicity I hardcoded txt, most important line is that of re.sub:
out = re.sub(r'1\.1\.1\.([1-3])','10.1.1.\g<1>0',txt)
It replaces substrings of txt which match first argument with second argument, first argument contain one group ([1-3]) which is later referenced in second argument (\g<1>) thus it in fact of executing following relacement:
1.1.1.1 to 10.1.1.10
1.1.1.2 to 10.1.1.20
1.1.1.3 to 10.1.1.30
However keep in mind that re.sub is working in single-pass fashion, unlike repeating usage of .replace method of str.

Extract line that matches with my criteria in Python

I have a variable with this inside:
Device ID: second-02 Entry address(es): IP address: 7.7.7.7
Platform: cisco WS-8PC-S, Capabilities: Router Switch IGMP Interface:
GigabitEthernet0/20, Port ID (outgoing port): GigabitEthernet0/11
Holdtime : 100 sec
Power request id: 0, Power management id: 1, Power available: 0, Power management level: -1 Management address(es): IP address:
7.7.7.7
Device ID: first-01 Entry address(es): IP address: 8.8.8.8 Platform:
cisco ME--12CS-A, Capabilities: Router Switch IGMP Interface:
GigabitEthernet0/11, Port ID (outgoing port): GigabitEthernet0/12
Holdtime : 158 sec Power request id: 0, Power management id: 0, Power available: 0, Power management level: 0 Management address(es): IP address: 8.8.8.8
How can I extract each IP address with its respective Device ID and output something like
Device ID: second-02 = IP address: 7.7.7.7
Device ID: first-01 = IP address: 8.8.8.8
Please note that for each Device ID we have one unique IP address but each one appears two times inside the main variable
So far Ive been able to verify if there is an IP address inside the variable with ip = re.findall( r'[0-9]+(?:\.[0-9]+){3}', s ) but that wont do the trick as I need each Device ID to be paired (or matched?) against its IP address.
How about using this regular expression:
r'Device ID: ([^ ]*) Entry address\(es\): IP address: ([^ ]*)'
https://regex101.com/r/BsIRh1/2

How do I use "not in" correctly to identify an item is not in a list of strings in Python?

I have a list of tuples comprised of an interface name and access list name. Like this:
exempt_int_acl_tuple=[('(app)', 'access-list nonat'), ('(app2)', 'access-list nonat')]
Sample config to search through is something like this:
config=['access-list nonat extended permit ip 10.0.0.0 255.0.0.0 10.0.0.0 255.0.0.0','access-list nonat extended permit ip 10.0.0.0 255.0.0.0 192.168.15.0 255.255.255.0','access-list nonat extended permit ip 10.0.0.0 255.0.0.0 1.1.1.1 255.255.255.240','blah','blah blah','some more blah']
I have a list strings in which I look for a specific pattern. If the list matches the pattern, I add it to a new list called exempt_acl.
So my code looks like this:
exempt_acl=[]
for interface,acl_name in exempt_int_acl_tuple:
for someline in config:
acl_statement=acl_name+' extended permit ip '
if (acl_statement in someline) and (someline not in exempt_acl):
exempt_acl.append(someline)
In this case, the access list name in the tuple is repeated so the config file is searched for twice. So exempt_acl looks like this:
['access-list nonat extended permit ip 10.0.0.0 255.0.0.0 10.0.0.0 255.0.0.0 ', 'access-list nonat extended permit ip 10.0.0.0 255.0.0.0 192.168.15.0 255.255.255.0 ', 'access-list nonat extended permit ip 10.0.0.0 255.0.0.0 1.1.1.1 255.255.255.240 ']
['access-list nonat extended permit ip 10.0.0.0 255.0.0.0 10.0.0.0 255.0.0.0 ', 'access-list nonat extended permit ip 10.0.0.0 255.0.0.0 192.168.15.0 255.255.255.0 ', 'access-list nonat extended permit ip 10.0.0.0 255.0.0.0 1.1.1.1 255.255.255.240 ']
However, the same lines are added twice, and it is creating a list of lists, instead of a list of strings. I thought the boolean evaluation for (someline not in exempt_acl) would prevent the line being added a second time, but it is. What am I doing wrong? I am using file.read().splitlines() to read the config file in case that makes a difference.
To start with, delistify someline by saying someline = someline[0].
If lines still appear to be appended twice, it probably comes down to the ambiguity of the condition if (acl_statement in someline) . If two lines are slightly different (e.g. different amounts of whitespace) but nonetheless contain the same acl_statement, both will be appended.
Assuming you have a file named config.txt with these contents:
access-list nonat extended permit ip 10.0.0.0 255.0.0.0 10.0.0.0 255.0.0.0
access-list nonat extended permit ip 10.0.0.0 255.0.0.0 192.168.15.0 255.255.255.0
access-list nonat extended permit ip 10.0.0.0 255.0.0.0 1.1.1.1 255.255.255.240
And test.py containing this:
exempt_acl = [('(app)', 'access-list nonat'),
('(app2)', 'access-list nonat'),
]
results = []
with open('config.txt') as config:
for line in config:
line = line.strip()
for _,acl_name in exempt_acl:
acl_statement = acl_name + ' extended permit ip '
if line.startswith(acl_statement) and line not in results:
results.append(line)
for result in results:
print(result)
That should do what you're looking for, if I understand your question correctly.
I'm not sure exactly what you were doing in your original code base - the code in your question was definitely not an MCVE. This code also works as expected:
config = [line.strip() for line in open('config.txt')]
results = []
for _, acl_name in exempt_acl:
for line in config:
acl_statement = acl_name + ' extended permit ip '
if acl_statement in line and line not in results:
results.append(line)
for result in results:
print(result)
I found the answer to why the configuration was being printed twice.
Main was calling the function twice. Once incorrectly (not mapping the function output to a value, but performing the printing involved in the debugging), and once correctly, hence not affecting output.
Thanks to all who assisted. I'm very grateful for your help, and I learned a few things too! The most important lesson for me was to formulate an MCVE before posting in the future.

Regex Match on IP Address Before Known String

I'm using nmap to search for hostnames and related IPs on my local (home) network. I can pull a string that looks something like this:
Starting Nmap 6.40 ( http://nmap.org ) at 2014-02-15 22:20 PST
Nmap scan report for 192.168.1.1
Host is up (0.00025s latency).
MAC Address: ZZ:ZZ:11:ZZ:ZZ:ZZ (Cisco-Linksys)
Nmap scan report for 192.168.1.2
Host is up (0.0084s latency).
MAC Address: ZZ:ZZ:A1:2E:ZZ:ZZ (Apple)
Nmap scan report for 192.168.1.9
Host is up (0.012s latency).
MAC Address: A4:ZZ:57:17:ZZ:ZZ (Seiko Epson)
Nmap scan report for 192.168.1.103
Host is up (0.036s latency).
MAC Address: ZZ:ZZ:6D:05:ZZ:ZZ (Apple)
I know that I can put together a regular expression to give me the IP address directly above the "Seiko Epson" line, but I cannot figure out how to do it.
I'm specifically looking for a way to find the IP address of the host that I'm searching for, I'm currently using:
(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)
to find IP addresses, but I do not know how to augment this to find the IP address above a given string.
If you get the whole input as a single string, then
You firstly search for a fixed string Nmap scan report for,
nextly remember a sequence 0-9 or . (which should be there) as the output IP address,
then skip until the MAC addr part (containing :),
skip until the next opening paren,
and finally check if the string inside parens is Seiko Epson.
Example:
>>> inp='''Starting Nmap 6.40 ( http://nmap.org ) at 2014-02-15 22:20 PST
... Nmap scan report for 192.168.1.1
... Host is up (0.00025s latency).
... MAC Address: ZZ:ZZ:11:ZZ:ZZ:ZZ (Cisco-Linksys)
... Nmap scan report for 192.168.1.2
... Host is up (0.0084s latency).
... MAC Address: ZZ:ZZ:A1:2E:ZZ:ZZ (Apple)
... Nmap scan report for 192.168.1.9
... Host is up (0.012s latency).
... MAC Address: A4:ZZ:57:17:ZZ:ZZ (Seiko Epson)
... Nmap scan report for 192.168.1.103
... Host is up (0.036s latency).
... MAC Address: ZZ:ZZ:6D:05:ZZ:ZZ (Apple)'''
>>> import re
>>> r1 = re.compile(r'Nmap scan report for ([0-9.]*)[^:]*[^(]*\(Seiko Epson\)')
>>> r1.search(inp).group(1)
'192.168.1.9'
The idea behind [^...]'s is finite state machine.

Categories