2

Здравствуйте.

Начал сегодня изучать python, ранее лишь немного был знаком с php и чуть игрался с bash'ем.
Нужна помощь и совет в следующей задаче:
существует файл на 15000 строк вида:

TEST-MIB::testSnmpMode.asExternalLink.192.168.2.1.192.23.151.44 = Hex-STRING: 00 01 20 05 00 00 00 00 D9 17 97 2C 80 01 02 D5 
D5 B4 00 24 FF 00 00 00 00 00 18 00 00 00 00 
00 00 00 0A 
TEST-MIB::testSnmpMode.asExternalLink.192.168.2.2.10.0.0.1 = Hex-STRING: 00 01 24 05 0A 0B 0A 00 D9 17 97 3E 80 00 07 73 
05 BD 00 24 FF FF FF 00 00 00 00 14 00 00 00 00 
00 00 00 00 
TEST-MIB::testSnmpMode.asExternalLink.192.168.2.3.167.3.1.2 = Hex-STRING: 00 01 20 05 0A 0D 14 00 D9 17 97 3E 80 00 07 73 
96 22 00 24 FF FF FF 00 00 00 00 15 00 00 00 00 
00 00 00 00

Из этого файла (на примере трех блоков выше) необходимо получить данные вида:

192.168.2.1/FF000000 192.23.151.44
192.168.2.2/FFFFFF00 10.0.0.1
192.168.2.3/FFFFFF00 167.3.1.2

Иными словами, спарсить значения, которые находятся между "asExternalLink." до первого пробела, заменить четвертую точку знаком "/" и пробелом, потом распарсить следующую строку, вытащить из неё маску в шестнадцатеричном формате (потом её как-то надо будет в нормальный вид привести, но это уже другая история) и добавить её в уже форматированную первую строку после знака "/". Маска всегда идет после числа 24 и всегда на следующей строке после "...asExternalLink...".
Я написал на питоне:

#!/usr/bin/env python
import re

snmpwalkfile = open ("test2")
raw_list = snmpwalkfile.read()
raw_ip = re.findall ( 'asExternalLink.(.*?) ', raw_list, re.DOTALL) # Получаю из большого файла отпарсенные строки вида "192.168.2.1.192.23.151.44"
raw_mask = re.findall ('24\x20(..\x20..\x20..\x20..)', raw_list, re.DOTALL) # Получаю из большого файла регуляркой выражение после числа 24

for ip in raw_ip:
    ip_list = re.findall ('^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', ip, re.DOTALL) # Из файла строк 192.168.2.1.192.23.151.44 выделяю только первый IP-адрес
    for ip_only in ip_list:
        print ip_only

for mask in raw_mask:
    print mask

snmpwalkfile.close()

Выводит на экран первый IP и маску в формате FF FF FF 00

Собственно, вопросов несколько:
1. В моменте, где я получаю значения после "24", возникает проблема, по регулярке '24\x20(..\x20..\x20..\x20..' можно найти ошибочные значения на первой строке (например, в исходном примере - "...Hex-STRING: 00 01 24 05 0A 0B 0A..."), что не нужно. То есть либо перед тем как выполнить регулярку удалить строки, содержащие, например, "asExternalLink", а потом выполнить поиск значений, либо как-то указать, чтобы он парсил значения только в следующей строке после строки, содержащей "asExternalLink".
Разобраться не могу с этим вопросом.
2. Сложить два списка в коде выше - ip_only и mask, не смог разобраться, в какой for это прописать и как это в принципе лучше сделать.
3. Далее сделать третий список из "вторых IP-адресов", то есть в исходнике выше это 192.23.151.44, 10.0.0.1 и т.д. Здесь я, наверное, сам разберусь, интересует, как потом полученный список сложить со списком iponly+mask.
Я наверняка перемудрил и затупил с логикой, может, кто-нибудь подскажет, как это сделать проще в плане логики и, главное, в плане кода? Начал изучать язык сегодня, но, к сожалению, много чего ещё не постиг.
Благодарен за совет.

4
  • Насколько Вы уверенны в формате входного файла? Это вопрос связан обработкой ошибок. К примеру что если очередная 5я строка от "TEST-MIB" будет содержать не "TEST-MIB", а "EST-MIB ?
    – sys_dev
    23 сен 2014 в 19:12
  • Должно быть так, проверял следующим образом: $ cat test2 | wc -l 15240 $ echo 15240/3 | bc 5080 $ grep TEST-MIB test2 | wc -l 5080 Не исключена возможность, что записей-то может быть и 5080, а строки в другом порядке, но маловероятно.
    – turbin
    23 сен 2014 в 19:24
  • @turbin: А можно ответить ровно в том формате на мои вопросы, как они заданы? Ведь человек может не использовать Linux, FreeBSD или еще что-нибудь и не понимать ваших магических wc, cat и др.
    – sys_dev
    23 сен 2014 в 19:31
  • Пока писал ответ на первый вопрос, второго не было, да и первый был задан немного иначе. 1. TEST-MIB::testSnmpMode.asExternalLink.192.168.2.1.192.23.151.44 = Hex-STRING: 00 01 20 05 00 00 00 00 D9 17 97 2C 80 01 02 D5 (1 строка) D5 B4 00 24 FF 00 00 00 00 00 18 00 00 00 00 (2 строка) 00 00 00 0A (3 строка) 2. Да, уверен, проверял наличие строк, содержащих слово TEST-MIB, так: а) посчитал количество строк в файле - 15240 б) разделил количество строк на три - 5080 в) нашел количество строк, в которых есть словосочетание TEST-MIB - 5080
    – turbin
    23 сен 2014 в 19:35

2 ответа 2

3

Учитывая, что входной файл очень регулярный, можно использовать простой хрупкий код, чтобы выполнить преобразования:

#!/usr/bin/env python
import re

def parse_ips(line):
    return re.match(r'TEST-MIB::testSnmpMode\.asExternalLink\.'
                    r'{ip}\.{ip}\s+='.format(ip=r"((?:\d{1,3}\.){3}\d{1,3})"),
                    line).groups()

def parse_netmask(line):
    mask = line[12:24] # treat it as a fixed format
    assert re.match("{hex} {hex} {hex} {hex}".format(hex=r'[0-9A-F]{2}'), mask)
    return ''.join(mask.split())

with open('test2') as file:
    for first, second, _ in zip(*[file]*3): # read 3 lines at a time
        ip1, ip2 = parse_ips(first)
        netmask = parse_netmask(second)
        print("{ip1}/{netmask} {ip2}".format(**vars()))

Любая ошибка во входном формате (например, пустая строка между записями или даже пробел в неожиданном месте) приведёт к генерации исключения и прекращению работы.

2

Исходя из ответов на мои уточняющие вопросы, сделал вывод, что логика должна быть такой:

  1. В одной функции: открывается объект файла, читается три строки. Но берутся только две первые строки и в виде touple бросаются на верх с помощью yield(). Третья читается "ровности" чтения строк.
  2. В другой функции: пишется регулярка на вычленение из первой строки ip-адресов.
  3. В другой функции: пишется регулярка на вычленение маски.

При использовании yield не будет оверхеда по памяти + можно писать результат сразу же и подавать на дальнейшую обработку по пайпу.

UPD

Поясню, читать строки из стрима можно по-разному:

  • readlines() - это все строки. Имеет смысл так делать, если стримы конечного размера и при этом небольшие по размеру;
  • readline() - читается одна строка.

Вот справка 7. Input and Output, рекомендую к прочтению.

yield - это специальный механизм, позволяющий Вам в вычислительном процессе остановиться на каком-то этапе и вернуть некий промежуточный результат дальше по процессу вычисления. При этом запомнится место, где произошла пауза. После того как промежуточный результат будет обработан, то возвратится вместо остановки и продолжится процесс снова.

Подробнее тут: Как работает yield.

UPD2:

// begin of first
(TEST-MIB.*asExternalLink.)([0-9]+.[0-9]+.[0-9]+.[0-9]+).([0-9]+.[0-9]+.[0-9]+.[0-9]+)( = Hex-STRING.*)
// end of first группы захвата $2, $3

UPD3:

// begin of second
([0-9A-F]{2} ){4}(([0-9A-F]{2} ){4})([0-9A-F]{2} ){8}
// end of second группа захвата $1
4
  • Вопросы глупые, но очень хочется разобраться: 1. Строки: TEST-MIB::testSnmpMode.asExternalLink.192.168.2.1.192.23.151.44 = Hex-STRING: 00 01 20 05 00 00 00 00 D9 17 97 2C 80 01 02 D5 (1 строка) D5 B4 00 24 FF 00 00 00 00 00 18 00 00 00 00(2 строка) 00 00 00 0A (3 строка) Читать три строки через readlines? Потом первые две в touple и yield наверх? Как-нибудь в цикле? То есть логически последовательность выборки строк такая (без оглядки на питон): 0, 1, 3, 4, 6, 7, 9, 10 и так далее. 1 пункт не очень понял, точнее совсем не понял. Очень был бы благодарен за пример кода по первому пункту.
    – turbin
    23 сен 2014 в 20:55
  • 3. Беда данной регулярки в том, что в строке номер один также может быть аналогичная конструкция: '24\x20(..\x20..\x20..\x20..)' То есть из того, что получится в пункте номер 1 (то есть без третьей строки) надо ещё выводить только, допустим, четные строки и потом уже на них применять регулярку, я правильно понимаю? И вопрос про склейку переменных, допустим первая строчка ip-адреса с маской вычленена, проверено, что количество ip и масок совпадает, как теперь их правильно склеить?
    – turbin
    23 сен 2014 в 20:55
  • 2
    @turbin > Читать три строки через readlines? Потом первые две в touple и yield наверх? Как-нибудь в цикле? давно не писал на питоне, примерно так (и то по-питоньему это все должно быть в классе, а не угорать по ФП): def get_next_lines(f): lines = (f.readline(), f.readline(),) f.readline() # читаем еще одну ненужную строку, продвигая тем самым курсор к следующей yield lines Таким образом за раз будет читаться по две строки из файла, что сводит использование оперативки практически к ничему.
    – etki
    23 сен 2014 в 21:45
  • Спасибо за разъяснения. В итоге выцепление масок решил так: with open("test2") as f: content = f.readlines() for i in xrange(1,len(content),3): print content[i] mask = re.findall ('^..\x20..\x20..\x20..\x20(..\x20..\x20..\x20..)', content[i], re.DOTALL) print mask
    – turbin
    24 сен 2014 в 7:36

Ваш ответ

By clicking “Отправить ответ”, you agree to our terms of service and acknowledge you have read our privacy policy.

Всё ещё ищете ответ? Посмотрите другие вопросы с метками или задайте свой вопрос.