Регулярные выражения php
Содержание:
- Строковые методы, поиск и замена
- regexp.test(str)
- Скобки в регулярных выражениях
- preg_split()
- Скобочные группы ― ()
- Повторения (квантификаторы)
- Опережающие и ретроспективные проверки — (?=) and (?
- Заключение
- Примеры preg_match PHP
- Бекслеши
- Алгоритм[править]
- Примеры шаблонов
- Статичные регэкспы
- Классы символовCharacter Classes
- Специальные конструкции в регулярках
- Parse Apache Logs
Строковые методы, поиск и замена
Следующие методы работают с регулярными выражениями из строк.
Все методы, кроме replace, можно вызывать как с объектами типа regexp в аргументах, так и со строками, которые автоматом преобразуются в объекты RegExp.
Так что вызовы эквивалентны:
var i = str.search(/\s/) var i = str.search("\\s")
При использовании кавычек нужно дублировать \ и нет возможности указать флаги. Если регулярное выражение уже задано строкой, то бывает удобна и полная форма
var regText = "\\s" var i = str.search(new RegExp(regText, "g"))
Возвращает индекс регулярного выражения в строке, или -1.
Если Вы хотите знать, подходит ли строка под регулярное выражение, используйте метод (аналогично RegExp-методы ). Чтобы получить больше информации, используйте более медленный метод (аналогичный методу ).
Этот пример выводит сообщение, в зависимости от того, подходит ли строка под регулярное выражение.
function testinput(re, str){ if (str.search(re) != -1) midstring = " contains "; else midstring = " does not contain "; document.write (str + midstring + re.source); }
Если в regexp нет флага , то возвращает тот же результат, что .
Если в regexp есть флаг , то возвращает массив со всеми совпадениями.
Чтобы просто узнать, подходит ли строка под регулярное выражение , используйте .
Если Вы хотите получить первый результат — попробуйте r.
В следующем примере используется, чтобы найти «Chapter», за которой следует 1 или более цифр, а затем цифры, разделенные точкой. В регулярном выражении есть флаг , так что регистр будет игнорироваться.
str = "For more information, see Chapter 3.4.5.1"; re = /chapter (\d+(\.\d)*)/i; found = str.match(re); alert(found);
Скрипт выдаст массив из совпадений:
- Chapter 3.4.5.1 — полностью совпавшая строка
- 3.4.5.1 — первая скобка
- .1 — внутренняя скобка
Следующий пример демонстрирует использование флагов глобального и регистронезависимого поиска с . Будут найдены все буквы от А до Е и от а до е, каждая — в отдельном элементе массива.
var str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; var regexp = //gi; var matches = str.match(regexp); document.write(matches); // matches =
Метод replace может заменять вхождения регулярного выражения не только на строку, но и на результат выполнения функции. Его полный синтаксис — такой:
var newString = str.replace(regexp/substr, newSubStr/function)
- Объект RegExp. Его вхождения будут заменены на значение, которое вернет параметр номер 2
- Строка, которая будет заменена на .
- Строка, которая заменяет подстроку из аргумента номер 1.
- Функция, которая может быть вызвана для генерации новой подстроки (чтобы подставить ее вместо подстроки, полученной из аргумента 1).
Метод не меняет строку, на которой вызван, а просто возвращает новую, измененную строку.
Чтобы осуществить глобальную замену, включите в регулярное выражение флаг .
Если первый аргумент — строка, то она не преобразуется в регулярное выражение, так что, например,
var ab = "a b".replace("\\s","..") // = "a b"
Вызов replace оставил строку без изменения, т.к искал не регулярное выражение , а строку «\s».
В строке замены могут быть такие спецсимволы:
Pattern | Inserts |
Вставляет «$». | |
Вставляет найденную подстроку. | |
Вставляет часть строки, которая предшествует найденному вхождению. | |
Вставляет часть строки, которая идет после найденного вхождения. | |
or | Где или — десятичные цифры, вставляет подстроку вхождения, запомненную -й вложенной скобкой, если первый аргумент — объект RegExp. |
Если Вы указываете вторым параметром функцию, то она выполняется при каждом совпадении.
В функции можно динамически генерировать и возвращать строку подстановки.
Первый параметр функции — найденная подстрока. Если первым аргументом является объект , то следующие параметров содержат совпадения из вложенных скобок. Последние два параметра — позиция в строке, на которой произошло совпадение и сама строка.
Например, следующий вызов возвратит XXzzzz — XX , zzzz.
function replacer(str, p1, p2, offset, s) { return str + " - " + p1 + " , " + p2; } var newString = "XXzzzz".replace(/(X*)(z*)/, replacer)
Как видите, тут две скобки в регулярном выражении, и потому в функции два параметра , .
Если бы были три скобки, то в функцию пришлось бы добавить параметр .
Следующая функция заменяет слова типа на :
function styleHyphenFormat(propertyName) { function upperToHyphenLower(match) { return '-' + match.toLowerCase(); } return propertyName.replace(//, upperToHyphenLower); }
regexp.test(str)
Метод ищет совпадение и возвращает , в зависимости от того, находит ли он его.
Например:
Пример с отрицательным ответом:
Если регулярное выражение имеет флаг , то ищет, начиная с и обновляет это свойство, аналогично .
Таким образом, мы можем использовать его для поиска с заданной позиции:
Одно и то же регулярное выражение, использованное повторно на другом тексте, может дать другой результат
Если мы применяем одно и то же регулярное выражение последовательно к разным строкам, это может привести к неверному результату, поскольку вызов обновляет свойство , поэтому поиск в новой строке может начаться с ненулевой позиции.
Например, здесь мы дважды вызываем для одного и того же текста, и второй раз поиск завершается уже неудачно:
Это именно потому, что во втором тесте не равен нулю.
Чтобы обойти это, можно присвоить перед новым поиском. Или вместо методов на регулярном выражении вызывать методы строк , они не используют .
Скобки в регулярных выражениях
Давай повторим, что обозначают разные виды скобок:
- Фигурные скобки задают число повторений предыдущего
символа — в этом примере выражение ищет от 1 до 5 идущих подряд
букв «a» - Квадратные скобки означают «один любой из
этих символов», в данном случае — буквы a, b, c, x, y, z или
цифра от 0 до 5. Внутри квадратных скобок не работают другие спецсимволы
вроде или — они обозначают обычный символ. Если
в квадратных скобках в начале стоит символ то смысл меняется
на противоположный: «любой один символ, кроме указанных» —
например значит «один любой символ,
кроме a, b или c». - Круглые скобки группируют символы и выражения. Например в
выражении знак «плюс» относится только
к букве c и это выражение ищет слова вроде abc, abcc, abccc. А если
поставить скобки то квантифиактор плюс относится
уже к последовательности и выражение ищет слова
abc, abcbc, abcbcbc
Примечание: в квадратных скобках можно указывать диапазоны
символов, но помни, что русская буква ё идет отдельно от
алфавита и чтобы написать «любая русская буква»,
надо писать .
preg_split()
Функция preg_split() аналогична split() за одним исключением — параметр шаблон может содержать регулярное выражение.
Синтаксис функции preg_split():
array preg_split(string шаблон, string строка ])
Необязательный параметр порог определяет максимальное количество элементов, на которые делится строка. В следующем примере функция preg_split() используется для выборки информации из переменной.
$user_info="+wj+++Gilmore+++++wjgi]more@hotmail.com+++++++Columbus+++OH"; $fields = preg_split("/\+{1.}/", $user_info); while($x < sizeof($fields)): print $fields. "<br>"; $x++; endwhile;
Результат:
WJ Gilmore wjgilmore@hotmail.com Columbus OH
Скобочные группы ― ()
a(bc) создаём группу со значением bc -> тестa(?:bc)* оперетор ?: отключает группу -> тестa(?<foo>bc) так, мы можем присвоить имя группе -> тест
Этот оператор очень полезен, когда нужно извлечь информацию из строк или данных, используя ваш любимый язык программирования. Любые множественные совпадения, по нескольким группам, будут представлены в виде классического массива: доступ к их значениям можно получить с помощью индекса из результатов сопоставления.
Если присвоить группам имена (используя ), то можно получить их значения, используя результат сопоставления, как словарь, где ключами будут имена каждой группы.
Повторения (квантификаторы)
Комбинация типа означает, что цифра должна повторяться два раза. Но бывают задачи, когда повторений очень много или мы не знаем, сколько именно. В таких члучаях нужно использовать специальные метасимволы.
Повторения символов или комбинаций описываются с помощью квантификаторов (метасимволов, которые задают количественные отношения). Есть два типа квантификаторов: общие (задаются с помощью фигурных скобок ) и сокращенные (сокращения наиболее распространенных квантификаторов). Фигурные скобки задают число повторений предыдущего символа (в этом случае выражение ищет от 1 до 7 идущих подряд букв «x»).
Квантификатор | Описанте |
---|---|
a+ | Один и более раз a |
a* | Ноль и более раз a |
a? | Одна a или пусто |
a{3} | 3 раза a |
a{3,5} | От 3 до 5 раз a |
a{3,} | 3 и более раз a |
Примечание: Если в выражении требуется поиск одного из метасимволов, вы можете использовать обратный слэш . Например, для поиска одного или нескольких вопросительных знаков можно использовать следующее выражение:
Опережающие и ретроспективные проверки — (?=) and (?
d(?=r) соответствует d, только если после этого следует r, но r не будет входить в соответствие выражения -> тест(?<=r)d соответствует d, только если перед этим есть r, но r не будет входить в соответствие выражения -> тест
Вы можете использовать оператор отрицания !
d(?!r) соответствует d, только если после этого нет r, но r не будет входить в соответствие выражения -> тест(?<!r)d соответствует d, только если перед этим нет r, но r не будет входить в соответствие выражения -> тест
Заключение
Как вы могли убедиться, области применения регулярных выражений разнообразны. Я уверен, что вы сталкивались с похожими задачами в своей работе (хотя бы с одной из них), например такими:
- Валидация данных (например, правильно ли заполнена строка time)
- Сбор данных (особенно веб-скрапинг, поиск страниц, содержащих определённый набор слов в определённом порядке)
- Обработка данных (преобразование сырых данных в нужный формат)
- Парсинг (например, достать все GET параметры из URL или текст внутри скобок)
- Замена строк (даже во время написания кода в IDE, можно, например преобразовать Java или C# класс в соответствующий JSON объект, заменить “;” на “,”, изменить размер букв, избегать объявление типа и т.д.)
- Подсветка синтаксиса, переименование файла, анализ пакетов и многие другие задачи, где нужно работать со строками (где данные не должны быть текстовыми).
Перевод статьи Jonny Fox: Regex tutorial — A quick cheatsheet by examples
Примеры preg_match PHP
1.
if (!preg_match("/^*\@*\.{2,6}$/i", $email)) exit("Неправильный адрес");
2.
// \S означает "не пробел", а + - // "любое число букв, цифр или точек". Модификатор 'i' после '/' // заставляет PHP не учитывать регистр букв при поиске совпадений. // Модификатор 's', стоящий рядом с 'i', говорит, что мы работаем // в "однострочном режиме" (см. ниже в этой главе). preg_match('/(\S+)@(+)/is', "Привет от somebody@mail.ru!", $p); // Имя хоста будет в $p, а имя ящика (до @) - в $p. echo "В тексте найдено: ящик - $p, хост - $p";
3.
if (!preg_match("|^{13,16}$|", $var)) ...
4.
if (preg_match("/(^+(*))$/" , $filename)==NULL) { echo "invalid filename"; exit; }
/\.(?:z(?:ip|{2})|r(?:ar|{2})|jar|bz2|gz|tar|rpm)$/i
/\.(?:mp3|wav|og(?:g|a)|flac|midi?|rm|aac|wma|mka|ape)$/i
/\.(?:exe|msi|dmg|bin|xpi|iso)$/i
/\.(?:jp(?:e?g|e|2)|gif|png|tiff?|bmp|ico)$/i
/\.(?:mpeg|ra?m|avi|mp(?:g|e|4)|mov|divx|asf|qt|wmv|m\dv|rv|vob|asx|ogm)$/i
5.
preg_match_all('/(8|7|\+7){0,1}{0,}({2}){0,}(({2}{0,}{2}{0,}{3})|({3}{0,}{2}{0,}{2})|({3}{0,}{1}{0,}{3})|({2}{0,}{3}{0,}{2}))/', $text, $regs );
6.
if (preg_match("/^{8,20}$/",$string)) echo "yes"; else echo "no";
7.абвгДДДеёааббаабб
if (preg_match("/(.)\\1\\1/",$string)) echo "yes"; else echo "no";
8.
preg_match("/abc/", $string); // true если найдёт в любом месте preg_match("/^abc/", $string); // true если найдёт в начале preg_match("/abc$/", $string); // true если найдёт в конце
9.
preg_match("/(ozilla.|MSIE.3)/i", $_SERVER);
Бекслеши
Если ты смотрел другие учебники по регулярным выражениям, то наверно заметил,
что бекслеш везде пишут по-разному. Где-то пишут один бекслеш:
, а здесь в примерах он повторен 2 раза: .
Почему?
Язык регулярных выражений требует писать бекслеш один раз. Однако в
строках в одиночных и двойных кавычках в PHP бекслеш тоже имеет особое
значение: .
Ну например, если написать то PHP воспримет это как
специальную комбинацию и вставит в строку только символ
(и движок регулярных выражений не узнает о бекслеше перед ним). Чтобы
вставить в строку последовательность , мы должны удвоить бекслеш
и записать код в виде .
По этой причине в некоторых случаях (там, где последовательность символов
имеет специальный смысл в PHP) мы обязаны удваивать бекслеш:
- Чтобы написать в регулярке , мы пишем в коде
- Чтобы написать в регулярке , мы удваиваем каждый
бекслеш и пишем - Чтобы написать в регулярке бекслеш и цифру (),
бекслеш надо удвоить:
В остальных случаях один или два бекслеша дадут один и тот же
результат: и вставят в строку пару
символов — в первом случае 2 бекслеша это последовательность
для вставки бекслеша, во втором случае специальной последовательности
нет и символы вставятся как есть. Проверить, какие символы вставятся в строку,
и что увидит движок регулярных выражений, можно с помощью
echo: . Да, сложно, а что поделать?
Алгоритм[править]
Данный алгоритм работает быстрее недетерминированного конечного автомата, построенного по теореме Клини, но только для регулярных выражений, состоящих из символов:
- — один любой буквенный символ,
- — один любой символ,
- — символ начала текста,
- — символ конца текста,
- — предыдущий символ встречается ноль или более раз.
Например, для , очевидно, проще написать простой сопоставитель, чем строить НКА.
Псевдокодправить
function match(regexp: String, text: String): boolean if regexp == '^' return matchHere(regexp, text) int i = 0 while i text.length if matchHere(regexp, text) return true i++ return false
Функция проверяет есть ли вхождение регулярного выражения в любом месте в пределах текста. Если существует более одного вхождения, то найдется самое левое и самое короткое.
Логика функции проста. Если — первый символ регулярного выражения, то любое возможное вхождение должно начинаться в начале текста. То есть если — регулярное выражение, то должно входить в текст только с первой позиции текста, а не где-то в середине текста. Это проверяется путем сопоставления остатка регулярного выражения с текстом, начиная с первой позиции и нигде более.
В противном случае регулярное выражение может входить в текст в любой позиции. Это проверяется путем сопоставления регулярного выражения во всех позициях текста. Если регулярное выражение входит более одного раза в текст, то только самое левое вхождение будет распознано. То есть если — регулярное выражение, то для него найдется самое левое вхождение в текст.
function matchHere(regexp: String, text: String): boolean if regexp == '\0' return true if regexp == '*' return matchStar(regexp, regexp, text) if regexp == '$' and regexp == '\0' return text == '\0' if text != '\0' and (regexp == '.' or regexp == text) return matchHere(regexp, text) return false
Основная часть работы сделана в , которая сопоставляет регулярное выражение с текстом в текущей позиции. Функция пытается сопоставить первый символ регулярного выражения с первым символом текста. В случае успеха мы можем сравнить следующий символ регулярного выражения со следующим символом текста, вызвав рекурсивно. Иначе нет совпадения с регулярным выражением в текущей позиции текста.
function matchStar(c: char, regexp: String, text: String): boolean int i = 0 while i text.length and (text == c or c == '.') if matchHere(regexp, text) return true i++ return false
Рассмотрим возможные случаи:
- Если в ходе рекурсии регулярное выражение осталось пустым то текст допускается этим регулярным выражением.
- Если регулярное выражение имеет вид , то вызывается функция которая пытается сопоставить повторение символа , начиная с нуля повторений и увеличивая их количество, пока не найдет совпадение с оставшимся текстом. Если совпадение не будет найдено, то регулярное выражение не допускает текст. Текущая реализация ищет «кратчайшее совпадение», которое хорошо подходит для сопоставления с образцом, как в grep, где нужно как можно быстрее найти совпадение. «Наидлиннейшее совпадение» более интуитивно и больше подходит для текстовых редакторов, где найденное заменят на что-то. Большинство современных библиотек для работы с регулярными выражениями предоставляют оба варианта.
- Если регулярное выражение это , то оно допускает этот текст тогда и только тогда, когда текст закончился.
- Если первый символ текста совпал с первым символом регулярного выражения, то нужно проверить совпадают ли следующий символ регулярного выражения со следующим символом текста, сделав рекурсивный вызов .
- Если все предыдущие попытки найти совпадения провалились, то никакая подстрока из текста не допускается регулярным выражением.
Модификацииправить
Немного изменим функцию для поиск самого левого и самого длинного вхождения :
- Найдем максимальную последовательность подряд идущих символов . Назовем ее .
- Сопоставим часть текста без с остатком регулярного выражения.
- Если части совпали, то текст допускается этим регулярным выражением. Иначе, если пусто, то текст не допускается этим регулярным выражением, иначе убираем один символ из и повторяем шаг .
Псевдокодправить
function matchStar(c: char, regexp: String, text: String): boolean int i for (i = 0; text != '\0' and (text == c or c == '.'); i++) while i 0 if matchHere(regexp, text) return true i-- return false
Примеры шаблонов
Начнем с пары простых примеров. Первое выражение на картинке ниже ищет
последовательность из 3 букв, где первая буква это «к», вторая — любая русская буква и
третья — это «т» без учета регистра (например, «кот» или «КОТ» подходит
под этот шаблон). Второе выражение ищет в тексте время в формате .
Любое выражение начинается с символа-ограничителя (delimiter по англ.). В качестве
него обычно используют символ , но можно использовать и другие
символы, не имеющие специального назначения в регулярках, например, ,
или . Альтернативные разделители используют, если в
выражении может встречаться символ . Затем идет сам шаблон строки,
которую мы ищем, за
ним второй ограничитель и в конце может идти одна или несколько букв-флагов. Они
задают дополнительные опции при поиске текста. Вот примеры флагов:
-
— говорит, что поиск должен вестись без учета
регистра букв (по умолчанию регистр учитывается) -
— говорит, что выражение и текст, по которому идет поиск,
исплоьзуют кодировку utf-8, а не только латинские буквы. Без него поиск
русских (и любых других нелатинских) символов может работать некорректно,
потому стоит ставить его всегда.
Сам шаблон состоит из обычных символов и специальных конструкций. Ну
например, буква «к» в регулярках обозначает саму себя, а вот символы
значат «в этом месте может быть любая цифра от 0 до 5». Вот полный список
специальных символов (в мануале php их называют метасимволы),
а все остальные символы в регулярке — обычные:
Ниже мы разберем значение каждого из этих символов (а также объясним почему буква
«ё» вынесена отдельно в первом выражении), а пока попробуем
применить наши регулярки к тексту и посмотреть, что выйдет. В php есть
специальная функция ,
которая принимает на вход регулярку, текст и пустой массив. Она проверяет,
есть ли в тексте подстрока, соответствующая данному шаблону и возвращает
, если нет,
или , если она есть. А в переданный массив в элемент с индексом
0 кладется первое найденное совпадение с регуляркой. Напишем простую
программу, применяющую регулярные выражения к разным строкам:
Код | Результат |
---|---|
Строка: рыжий кот + Найдено слово 'кот' Строка: рыжий крот - Ничего не найдено Строка: кит и кот + Найдено слово 'кит' |
Познакомившись с примером, изучим регулярные выражения более подробно.
Статичные регэкспы
В некоторых реализациях javascript регэкспы, заданные коротким синтаксисом /…/ — статичны. То есть, такой объект создается один раз в некоторых реализациях JS, например в Firefox. В Chrome все ок.
function f() { // при многократных заходах в функцию объект один и тот же var re = /lalala/ }
По стандарту эта возможность разрешена ES3, но запрещена ES5.
Из-за того, что при глобальном поиске меняется, а сам объект регэкспа статичен, первый поиск увеличивает , а последующие — продолжают искать со старого , т.е. могут возвращать не все результаты.
При поиске всех совпадений в цикле проблем не возникает, т.к. последняя итерация (неудачная) обнуляет .
Классы символовCharacter Classes
Класс символов соответствует какому-либо одному набору символов.A character class matches any one of a set of characters. Классы символов состоят из языковых элементов, приведенных в следующей таблице.Character classes include the language elements listed in the following table. Дополнительные сведения см. в разделе Классы символов.For more information, see Character Classes.
Класс знаковCharacter class | ОписаниеDescription | ШаблонPattern | Число соответствийMatches |
---|---|---|---|
character_group character_group | Соответствует любому одиночному символу, входящему в character_group.Matches any single character in character_group. По умолчанию при сопоставлении учитывается регистр.By default, the match is case-sensitive. | в in , в , in | |
character_group character_group | Отрицание: соответствует любому одиночному символу, не входящему в character_group.Negation: Matches any single character that is not in character_group. По умолчанию символы в character_group чувствительны к регистру.By default, characters in character_group are case-sensitive. | , , в , , in | |
first last first last | Диапазон символов: соответствует одному символу в диапазоне от первого до последнего.Character range: Matches any single character in the range from first to last. | , в , in | |
Подстановочный знак: соответствует любому одиночному символу, кроме \n.Wildcard: Matches any single character except \n.Для сопоставления символа точки (.To match a literal period character (. или ) перед ней нужно поставить дополнительную обратную косую черту ().or ), you must precede it with the escape character (). | в in в in | ||
имя name | Соответствует любому одиночному символу в общей категории Юникода или в именованном блоке, указанном в параметре имя.Matches any single character in the Unicode general category or named block specified by name. | , в , in , в , in | |
имя name | Соответствует любому одиночному символу, не входящему в общую категорию Юникода или в именованный блок, указанный в параметре имя.Matches any single character that is not in the Unicode general category or named block specified by name. | , , в , , in , в , in | |
Соответствует любому алфавитно-цифровому знаку.Matches any word character. | , , , , в , , , , in | ||
Соответствует любому символу, который не является буквенно-цифровым знаком.Matches any non-word character. | , в , in | ||
Соответствует любому знаку пробела.Matches any white-space character. | в in | ||
Соответствует любому знаку, не являющемуся пробелом.Matches any non-white-space character. | в in | ||
Соответствует любой десятичной цифре.Matches any decimal digit. | в in | ||
Соответствует любому символу, не являющемуся десятичной цифрой.Matches any character other than a decimal digit. | , , , , в , , , , in |
Специальные конструкции в регулярках
-
ищет одну любую цифру, — один
любой символ, кроме цифры -
соответствует одной любой букве (любого алфавита), цифре
или знаку подчеркивания . соответствует
любому символу, кроме буквы, цифры, знака подчеркивания.
Также, есть удобное условие для указания на границу слова: .
Эта конструкция обозначает, что с одной стороны от нее должен стоять символ,
являющийся буквой/цифрой/знаком подчеркивания (), а с
другой стороны — не являющийся. Ну, например, мы хотим найти в тексте слово
«кот». Если мы напишем регулярку , то она
найдет последовательность этих букв в любом месте — например, внутри слова
«скотина». Это явно не то, что мы хотели. Если же мы добавим
условие границы слова в регулярку: , то теперь
искаться будет только отдельно стоящее слово «кот».
Parse Apache Logs
Most websites are running on the Apache webserver. If your website does, you can easily use PHP and regular expressions to parse Apache logs.
//Logs: Apache web server //Successful hits to HTML files only. Useful for counting the number of page views. '^((?#client IP or domain name)S+)s+((?#basic authentication)S+s+S+)s+]+)]s+"(?:GET|POST|HEAD) ((?#file)/+?.html?)??((?#parameters)+)? HTTP/+"s+(?#status code)200s+((?#bytes transferred)+)s+"((?#referrer)*)"s+"((?#user agent)*)"$' //Logs: Apache web server //404 errors only '^((?#client IP or domain name)S+)s+((?#basic authentication)S+s+S+)s+]+)]s+"(?:GET|POST|HEAD) ((?#file)+)??((?#parameters)+)? HTTP/+"s+(?#status code)404s+((?#bytes transferred)+)s+"((?#referrer)*)"s+"((?#user agent)*)"$'
» Source