Конечно многие сочтут лишним объяснения КАК работают регулярные выражения, но мне почему-то кажется, что 99% сложностей как раз и связано с непониманием принципа работы RE. Потому я решил нелишним напомнить, как происходит разбор RE.
Рассмотрим простейшее регулярное выражение
/A/
Это выражение ищет в строке букву 'A'. Реализация тривиальная:
Это всё прекрасно, однако что делать если шаблон более сложен? Например
/ABC/
Для этого можно просто немного изменить наш алгоритм: после нахождения 'A' мы проверяем следующие буквы, до тех пор, пока не найдём полное совпадение:
Это работает, но очень медленно: во первых мы часто будем просматривать один символ множество раз (особенно если в нашей строке много букв 'A'), кроме того, при каждом просмотре необходимо проверить, есть-ли ещё символы (проверка у нас есть, но она проверяет только наличие s[i], а вот есть-ли у нас s[i+1] и s[i+2] - неизвестно). Что-бы это исправить, мы сделаем не одну, а три проверки:
Нам понадобится пара временных переменных: e1, которая истинна если выполнился п1, а так-же e2, которая истинна если выполнился п2. Первый пункт мы выполняем всегда, второй только при истинности e1, а третий - если истинно e2. Если на третьем пункте условие выполнилось - шаблон найден.
Получается примерно следующая схема:
Тут я для единообразия добавил e0, которая всегда истинна.
Ну и где-же тут ускорение? А ускорение тут в том, что за раз мы проверяем по одному условию, но наши процессоры умеют большее: мой например умеет проверять сразу 32 условия, т.к. он 32х битный (я выделил эту проверку в жёлтый прямоугольник). По этой причине я могу все три (максимум 32) проверки сделать за одну операцию, только для каждой проверки мне понадобится таблица в 256 бит (по числу символов), например для буквы «A» моя таблица будет состоять из одних нулей, кроме A-ного символа (65го в ASCII), тогда мне просто надо будет вынуть из таблицы бит, номер которого совпадает с номером символа. А раз я проверяю за раз по 32 условия, мне понадобится 32 таких таблицы, что составляет ровно 1 килобайт. Весь этот набор таблиц я могу вычислить перед поиском, а потом использовать его в случае, если мне опять понадобится поиск по этому шаблону.
export LC_COLLATE=ru_RU.UTF-8 export LC_CTYPE=ru_RU.UTF-8
Кроме ускорения поиска, при таком методе я могу очень просто реализовать нечёткий поиск: к примеру в таблицах для символов (тех, что в 256 бит) я могу помечать единицей не только A, B, и C, но и a, b, и c - это приведёт к тому, что мой шаблон будет работать и с малыми и с большими буквами, например так:
/[Aa][Bb][Cc]/
или вот так:
/ABC/I
(это верно для адресного выражения, для команды s нужно использовать модификатор i
).
В действительности, поиск происходит немного по другому: строка просматривается слева направо, но при нахождении поиск не прерывается, просто запоминается позиция начала совпадения. После чего просмотр продолжается в поисках завершения совпадения. Для простых шаблонов это не играет никакой роли, ведь начало совпадения можно легко рассчитать когда совпадение будет полностью найдено. Однако, даже в простых случаях следует учитывать тот факт, что поиск производится по всей строке, а не только до первого совпадения. Именно по этой причине возможно применение числовых модификаторов команды s. По умолчанию полагается модификатор 1, что приводит к запоминанию первого совпадения, кроме того, мы можем указать например модификатор 7, что приведёт к запоминанию именно седьмого совпадения (если оно произойдёт конечно).
s/(.*)X/\1Y/Что приведёт к замене именно последней X на Y.
/<a href="([^"]+)"[^>]*>/
Шаблоны с квантификаторами обрабатываются так-же: сначала ищется первое совпадение, а потом поиск повторяется необходимое число раз. К примеру /X{1,2}/ найдёт первое X и запомнит его позицию, а далее будет снова искать X, если он найдётся, то завершением совпадения считается окончание этого второго X (конечно X может быть любым подвыражением, а не только одиночным символом). Если второе X не найдётся, окончанием будет служить позиция окончания первого X (эта позиция тоже запоминается, но если будет найдено второе X, то новая позиция завершения затирает старую). Как видите, sed никогда не возвращается к уже просмотренным символам.
Вы можете обсудить этот документ на форуме. Текст предоставляется по лицензии GNU Free Documentation License (Перевод лицензии GFDL).
Вы можете пожертвовать небольшую сумму яндекс-денег на счёт 41001666004238 для оплаты хостинга, интернета, и прочего. Это конечно добровольно, однако это намного улучшит данный документ (у меня будет больше времени для его улучшения). На самом деле, проект часто находится на грани закрытия, ибо никаких денег никогда не приносил, и приносить не будет. Вы можете мне помочь. Спасибо.