Я пишу парсер для анализа вывода ниже:
admin@str-s6000-on-5:~$ show interface status Ethernet4
Interface Lanes Speed MTU Alias Vlan Oper Admin Type Asym PFC
--------------- ----------- ------- ----- ------------ ------ ------ ------- -------------- ----------
Ethernet4 29,30,31,32 40G 9100 fortyGigE0/4 trunk up up QSFP+ or later off
PortChannel0001 N/A 40G 9100 N/A routed up up N/A N/A
PortChannel0002 N/A 40G 9100 N/A routed up up N/A N/A
PortChannel0003 N/A 40G 9100 N/A routed up up N/A N/A
PortChannel0004 N/A 40G 9100 N/A routed up up N/A N/A
Я попытался написать регулярное выражение, соответствующее всем полям, как показано ниже.
(\S+)\s+([\d,]+)\s+(\S+)\s+(\d+)\s+(\S+)\s+(\S+)\s+([up|down])+\s+([up|down]+)\s+([\w\s+?]+)\s+(\S+)
Я могу правильно попасть в столбец «Администратор». Тип столбца содержит несколько слов, поэтому я использовал шаблон ([\w\s+?]+)
, надеясь, что он будет соответствовать нескольким рабочим данным, разделенным одним пробелом, где + является необязательным, за которым следует (\S+)
, чтобы соответствовать последнему столбцу. Проблема, с которой я сталкиваюсь, заключается в том, что регулярное выражение ([\w\s+?]+)
порождает несколько строк и дает мне результат, как показано ниже.
Ethernet4 29,30,31,32 40G 9100 fortyGigE0/4 trunk up up QSFP+ or later off PortChannel0001 N/A
Я вижу, что \s
также соответствует новой строке. как ограничить это, чтобы оно не соответствовало новой строке? может кто-нибудь, пожалуйста, помогите мне разъяснить.
Я посмотрел на это пространство Regex для одного или нескольких слов, разделенных пробелами, но это мне тоже не помогает. может кто-нибудь помочь мне понять это лучше?
Вы можете использовать такой шаблон, как:
^\s*(\S+)\s+([\d,]+)\s+(\S+)\s+(\d+)\s+(\S+)\s+(\S+)\s+(up|down)\s+(up|down)\s+(.*?)\s+(\S+)\s*$
^
и $
, вы можете ограничить свой шаблон.(.*?)
просто извлекает ваш 9-й столбец.import re
s = " Ethernet4 29,30,31,32 40G 9100 fortyGigE0/4 trunk up up QSFP+ or later off "
p = r'^\s*(\S+)\s+([\d,]+)\s+(\S+)\s+(\d+)\s+(\S+)\s+(\S+)\s+(up|down)\s+(up|down)\s+(.*?)\s+(\S+)\s*$'
print(re.findall(p, s))
[('Ethernet4', '29,30,31,32', '40G', '9100', 'fortyGigE0/4', 'trunk', 'up', 'up', 'QSFP+ or later', 'off')]
При необходимости вы также можете добавить соответствующие флаги: (?im)^\s*(\S+)\s+([\d,]+)\s+(\S+)\s+(\d+)\s+(\S+)\s+(\S+)\s+(up|down)\s+(up|down)\s+(.*?)\s+(\S+)\s*$
Поскольку \s
также может соответствовать символам новой строки, вы можете использовать класс отрицательных символов [^\S\n]
для сопоставления пробельных символов без символов новой строки.
Похоже, ваши данные разделены двумя или более пробелами, поэтому вы можете использовать [^\S\n]{2,}
Предпоследняя группа может быть не жадной ([\w +?]+?)
, но поскольку там также есть пробел, вы также можете сопоставить хотя бы один символ слова (\w[\w +?]*?)
Обратите внимание, что ([up|down])
— это класс символов, соответствующий одному из перечисленных символов, и это не то же самое, что (up|down)
, который представляет собой группу, соответствующую одной из альтернатив.
Например:
^[^\S\n]*(\S+)[^\S\n]{2,}([\d,]+)[^\S\n]{2,}(\S+)[^\S\n]{2,}(\d+)[^\S\n]{2,}(\S+)[^\S\n]{2,}(\S+)[^\S\n]{2,}(up|down)+[^\S\n]{2,}(up|down)+[^\S\n]{2,}(\w[\w +?]*?)[^\S\n]{2,}(\S+)$
Посмотрите демонстрацию регулярных выражений
Если разделительный пробел также может быть 1 вместо 2, вы также можете написать шаблон, как в этом примере.
хорошее использование [^\S\n]
по всему регулярному выражению
Вы можете построить регулярное выражение программно (10 столбцов = повторить один и тот же шаблон 10 раз):
import re
s = """\
Interface Lanes Speed MTU Alias Vlan Oper Admin Type Asym PFC
--------------- ----------- ------- ----- ------------ ------ ------ ------- -------------- ----------
Ethernet4 29,30,31,32 40G 9100 fortyGigE0/4 trunk up up QSFP+ or later off
PortChannel0001 N/A 40G 9100 N/A routed up up N/A N/A
PortChannel0002 N/A 40G 9100 N/A routed up up N/A N/A
PortChannel0003 N/A 40G 9100 N/A routed up up N/A N/A
PortChannel0004 N/A 40G 9100 N/A routed up up N/A N/A
"""
print(*re.findall(r"\s*(\S+(?:\s\S+)*)" * 10 + "$", s, flags=re.M)[2:], sep = "\n")
Распечатки:
('Ethernet4', '29,30,31,32', '40G', '9100', 'fortyGigE0/4', 'trunk', 'up', 'up', 'QSFP+ or later', 'off')
('PortChannel0001', 'N/A', '40G', '9100', 'N/A', 'routed', 'up', 'up', 'N/A', 'N/A')
('PortChannel0002', 'N/A', '40G', '9100', 'N/A', 'routed', 'up', 'up', 'N/A', 'N/A')
('PortChannel0003', 'N/A', '40G', '9100', 'N/A', 'routed', 'up', 'up', 'N/A', 'N/A')
('PortChannel0004', 'N/A', '40G', '9100', 'N/A', 'routed', 'up', 'up', 'N/A', 'N/A')
Привет @Андрей, спасибо, что нашел время ответить... просто пытаюсь немного понять. что делает *
перед re.findall
? . А не могли бы вы объяснить назначение "$"
в конце? это указывает на конец строки?
@Vijay string * 10 означает: повторите эту строку 10 раз. Да, $ означает конец строки. Вот почему у меня установлен флаг re.M.
Нет нет. я пытался понять *
перед re.findall
Предположим, для простоты, данные были следующими.
str = """
admin@str-s6000-on-5:~$ show interface status Ethernet4
Interface Lanes MTU Alias Ad Type Asym PFC
--------------- -------- ---- ------ ---- ----------- ----------
Ethernet4 29,30,31 9100 fG0/4 up Q+ or later off
PortChannel0001 N/A 9100 N/A up N/A N/A
"""
Я бы посоветовал вам использовать как подробный режим (также известный как свободный интервал) (re.VERBOSE
), так и именованные группы захвата, чтобы регулярное выражение самодокументировалось:
import re
rgx = r"""
^ # match beginning of line
[ ]* # match zero or more spaces
(?P<Interface>\S+) # match one or more non-whitespaces and
# save to capture group 'Interface'
[ ]+
(?P<Lanes>\d+(?:,\d+)*) # match one or more strings of two or
# more digits separated by a comma and
# save to capture group 'Lanes'
[ ]+
(?P<MTU>\d+) # match one or more digits and save to
# capture group 'MTU'
[ ]+
(?P<Alias>\S+) # match one or more non-whitespaces and
# save to capture group 'Alias'
[ ]+
(?P<Ad>up|down) # match 'up' or 'down' and save to
# capture group 'Ad'
[ ]+
(?P<Type>\S+(?:[ ]\S+)*) # match one or more groups of
# non-whitespaces separated by one
# space and save to capture group 'Type'
[ ]*
(?P<Asym_PFC>off|on) # match 'up' or 'down' and save to
# capture group 'Asym_PFC'
[ ]*
$ # match end of line
"""
Примечание. Я предположил, что пробелы в тексте — это просто пробелы (а не табуляции, например), и в этом случае предпочтительнее использовать пробелы в выражении. Кроме того, я написал, что каждое пространство находится в группе захвата ([ ]
); в противном случае оно будет удалено вместе с пробелами, которые не являются частью выражения. Есть и другие способы защитить помещения, один из которых — сбежать из них (\
).
Затем вы можете извлечь содержимое групп захвата следующим образом.
match = re.search(rgx, str, re.VERBOSE | re.MULTILINE)
if match:
print("capture group 'Interface': ", match.group('Interface'))
print("capture group 'Lanes': ", match.group('Lanes'))
print("capture group 'MTU': ", match.group('MTU'))
print("capture group 'Alias': ", match.group('Alias'))
print("capture group 'Ad': ", match.group('Ad'))
print("capture group 'Type': ", match.group('Type'))
print("capture group 'Asym_PFC': ", match.group('Asym_PFC'))
else:
print('did not find')
который отображает
capture group 'Interface': Ethernet4
capture group 'Lanes': 29,30,31
capture group 'MTU': 9100
capture group 'Alias': fG0/4
capture group 'Ad': up
capture group 'Type': Q+ or later
capture group 'Asym_PFC': off
Обратите внимание, что может быть достаточно определить, является ли каждая строка хранителем, и если она просто разбивает строку на регулярное выражение {2,}
; то есть разделить на два или более пробелов (после удаления пробелов в начале и/или конце строки). Если, например, линии интереса начинаются 'Ethernet'
, возможно, дополненные пробелами слева, мы могли бы использовать регулярное выражение r'^ *Ethernet.*'
для идентификации линий интереса и r' {2,}'
для разделения этих строк и присвоения частей переменным.
Я новичок в Python, если это видно.
Большое спасибо @cary Swowland за прекрасное объяснение и читаемый синтаксис. Я не знал, что мы можем так чисто написать регулярное выражение. Еще раз спасибо.
Возможно вот так
^[^\S\n]*(\S+)[^\S\n]{2,}([\d,]+)[^\S\n]{2,}(\S+)[^\S\n]{2,}(\d+)[^\S\n]{2,}(\S+)[^\S\n]{2,}(\S+)[^\S\n]{2,}(up|down)+[^\S\n]{2,}(up|down)+[^\S\n]{2,}([\w +?]+)[^\S\n]{2,}(\S+)
См. regex101.com/r/2NqUGX/1