Символы повторения

  • regex+ - одно или более повторений предшествующего элемента
  • regex* - ноль или более повторений предшествующего элемента
  • regex? - ноль или одно повторение предшествующего элемента
  • regex{n} - ровно n повторений предшествующего элемента
  • regex{n,m} - от n до m повторений предшествующего элемента
  • regex{n,} - n или более повторений предшествующего элемента

+

Плюс указывает, что предыдущее выражение может повторяться сколько угодно раз, но, как минимум, один раз.

Например, тут повторение относится к букве a:

In [1]: line = '100     aab1.a1a1.a5d3    FastEthernet0/1'

In [2]: re.search(r'a+', line).group()
Out[2]: 'aa'

А в этом выражении повторяется строка „a1“:

In [3]: line = '100     aab1.a1a1.a5d3    FastEthernet0/1'

In [4]: re.search(r'(a1)+', line).group()
Out[4]: 'a1a1'

В выражении ``(a1)+`` скобки используются для того, чтобы указать,
что повторение относится к последовательности символов 'a1'.

IP-адрес можно описать выражением \d+\.\d+\.\d+\.\d+. Тут плюс используется, чтобы указать, что цифр может быть несколько. А также встречается выражение \..

Оно необходимо из-за того, что точка является специальным символом (она обозначает любой символ). И чтобы указать, что нас интересует именно точка, надо ее экранировать - поместить перед точкой обратный слеш.

Используя это выражение, можно получить IP-адрес из строки sh_ip_int_br:

In [5]: sh_ip_int_br = 'Ethernet0/1    192.168.200.1   YES NVRAM  up          up'

In [6]: re.search(r'\d+\.\d+\.\d+\.\d+', sh_ip_int_br).group()
Out[6]: '192.168.200.1'

Еще один пример выражения: \d+\s+\S+ - оно описывает строку, в которой идут сначала цифры, после них пробельные символы, а затем непробельные символы (все, кроме пробела, таба и других подобных символов). С его помощью можно получить VLAN и MAC-адрес из строки:

In [7]: line = '1500     aab1.a1a1.a5d3    FastEthernet0/1'

In [8]: re.search(r'\d+\s+\S+', line).group()
Out[8]: '1500     aab1.a1a1.a5d3'

*

Звездочка указывает, что предыдущее выражение может повторяться 0 или более раз.

Например, если звездочка стоит после символа, она означает повторение этого символа.

Выражение ba* означает b, а затем ноль или более повторений a:

In [9]: line = '100     a011.baaa.a5d3    FastEthernet0/1'

In [10]: re.search(r'ba*', line).group()
Out[10]: 'baaa'

Если в строке line до подстроки baaa встретится b, то совпадением будет b:

In [11]: line = '100     ab11.baaa.a5d3    FastEthernet0/1'

In [12]: re.search(r'ba*', line).group()
Out[12]: 'b'

Допустим, необходимо написать регулярное выражение, которое описывает электронные адреса в двух форматах: user@example.com и user.test@example.com. То есть, в левой части адреса может быть или одно слово, или два слова, разделенные точкой.

Первый вариант на примере адреса без точки:

In [13]: email1 = 'user1@gmail.com'

Этот адрес можно описать таким выражением \w+@\w+\.\w+:

In [14]: re.search(r'\w+@\w+\.\w+', email1).group()
Out[14]: 'user1@gmail.com'

Но такое выражение не подходит для электронного адреса с точкой:

In [15]: email2 = 'user2.test@gmail.com'

In [16]: re.search(r'\w+@\w+\.\w+', email2).group()
Out[16]: 'test@gmail.com'

Регулярное выражение для адреса с точкой:

In [17]: re.search(r'\w+\.\w+@\w+\.\w+', email2).group()
Out[17]: 'user2.test@gmail.com'

Чтобы описать оба варианта адресов, надо указать, что точка в адресе опциональна:

'\w+\.*\w+@\w+\.\w+'

Такое регулярное выражение описывает оба варианта:

In [18]: email1 = 'user1@gmail.com'

In [19]: email2 = 'user2.test@gmail.com'

In [20]: re.search(r'\w+\.*\w+@\w+\.\w+', email1).group()
Out[20]: 'user1@gmail.com'

In [21]: re.search(r'\w+\.*\w+@\w+\.\w+', email2).group()
Out[21]: 'user2.test@gmail.com'

?

В последнем примере регулярное выражение указывает, что точка необязательна, но в то же время определяет, что она может появиться много раз.

В этой ситуации логичней использовать знак вопроса. Он обозначает ноль или одно повторение предыдущего выражения или символа. Теперь регулярное выражение выглядит так \w+\.?\w+@\w+\.\w+:

In [22]: mail_log = ['Jun 18 14:10:35 client-ip=154.10.180.10 from=user1@gmail.com, size=551',
    ...:             'Jun 18 14:11:05 client-ip=150.10.180.10 from=user2.test@gmail.com, size=768']

In [23]: for message in mail_log:
     ...:     match = re.search(r'\w+\.?\w+@\w+\.\w+', message)
     ...:     if match:
     ...:         print("Found email: ", match.group())
     ...:
Found email:  user1@gmail.com
Found email:  user2.test@gmail.com

{n}

С помощью фигурных скобок можно указать, сколько раз должно повторяться предшествующее выражение.

Например, выражение \w{4}\.\w{4}\.\w{4} описывает 12 букв или цифр, которые разделены на три группы по четыре символа точками. Таким образом можно получить MAC-адрес:

In [24]: line = '100     aab1.a1a1.a5d3    FastEthernet0/1'

In [25]: re.search(r'\w{4}\.\w{4}\.\w{4}', line).group()
Out[25]: 'aab1.a1a1.a5d3'

В фигурных скобках можно указывать и диапазон повторений. Например, попробуем получить все номера VLAN из строки mac_table:

In [26]: mac_table = '''
    ...: sw1#sh mac address-table
    ...:           Mac Address Table
    ...: -------------------------------------------
    ...:
    ...: Vlan    Mac Address       Type        Ports
    ...: ----    -----------       --------    -----
    ...:  100    a1b2.ac10.7000    DYNAMIC     Gi0/1
    ...:  200    a0d4.cb20.7000    DYNAMIC     Gi0/2
    ...:  300    acb4.cd30.7000    DYNAMIC     Gi0/3
    ...: 1100    a2bb.ec40.7000    DYNAMIC     Gi0/4
    ...:  500    aa4b.c550.7000    DYNAMIC     Gi0/5
    ...: 1200    a1bb.1c60.7000    DYNAMIC     Gi0/6
    ...: 1300    aa0b.cc70.7000    DYNAMIC     Gi0/7
    ...: '''

Так как search ищет только первое совпадение, в выражение \d{1,4} попадет номер VLAN:

In [27]: for line in mac_table.split('\n'):
    ...:     match = re.search(r'\d{1,4}', line)
    ...:     if match:
    ...:         print('VLAN: ', match.group())
    ...:
VLAN:  1
VLAN:  100
VLAN:  200
VLAN:  300
VLAN:  1100
VLAN:  500
VLAN:  1200
VLAN:  1300

Выражение \d{1,4} описывает от одной до четырех цифр.

Обратите внимание, что в выводе команды с оборудования нет VLAN с номером 1. При этом регулярное выражение получило откуда-то число 1. Цифра 1 попала в вывод из имени хоста в строке sw1#sh mac address-table.

Чтобы исправить это, достаточно дополнить выражение и указать, что после цифр должен идти хотя бы один пробел:

In [28]: for line in mac_table.split('\n'):
    ...:     match = re.search(r'\d{1,4} +', line)
    ...:     if match:
    ...:         print('VLAN: ', match.group())
    ...:
VLAN:  100
VLAN:  200
VLAN:  300
VLAN:  1100
VLAN:  500
VLAN:  1200
VLAN:  1300