CascadiaPHP 2024

Экранирующие последовательности

У обратного слеша «\» несколько назначений. Главное — если обратный слеш предшествует небуквенно-цифровому символу, обратный слеш снимает с этого символа специальное значение, которое символ мог иметь. Обратный слеш как экранирующий символ допустим как в символьном классе, так и вне него.

Например, для сопоставления символа «*» в шаблоне указывают «\*». Такой подход применяют независимо от того, интерпретировал бы парсер символ, который идёт за обратным слешем, как специальный без экранирования или нет. Поэтому небуквенно-цифровые символы безопасно экранировать символом обратного слеша «\», чтобы указать, что символ означает сам себя. Для сопоставления обратного слеша с самим собой, записывают «\\».

Замечание:

Обратный слеш в одинарных и двойных PHP-строках работает как служебный символ. Поэтому когда нужно сопоставить символ \ с регулярным выражением \\, в PHP-коде указывают "\\\\" или '\\\\'.

Если указали модификатор PCRE_EXTENDED, пробельные символы в шаблоне вне описания символьного класса парсер проигнорирует. Также игнорируется часть строки, которая находится между символом «#», который, опять же, не участвует в описании символьного класса, и следующим символом перевода строки. В этой ситуации обратный слеш указывают как экранирующий символ для обозначения вхождений пробельных символов или символа «#» в шаблоне.

Второе назначение обратного слеша — кодировать непечатаемые символы в шаблоне в видимой форме. Нет ограничений на появление непечатаемых символов, кроме двоичного нуля, который завершает шаблон, но при подготовке шаблона в текстовом редакторе проще записывать следующие экранирующие последовательности, чем двоичный символ, который последовательности представляют:

\a
символ оповещения, сигнал, (BEL, шестнадцатеричный код 07)
\cx
«control + x», где x — произвольный символ
\e
escape (шестнадцатеричный код 1B)
\f
разрыв страницы (шестнадцатеричный код 0C)
\n
перевод строки (шестнадцатеричный код 0A)
\p{xx}
символ со свойством xx, подробнее об этом рассказывает раздел «Свойства Unicode-символов»
\P{xx}
символ без свойства xx, подробнее об этом рассказывает раздел «Свойства Unicode-символов»
\r
возврат каретки (шестнадцатеричный код 0D)
\R
разрыв строки: совпадает с \n, \r и \r\n
\t
табуляция (шестнадцатеричный код 09)
\xhh
символ с шестнадцатеричным кодом hh
\ddd
символ с восьмеричным кодом ddd, либо ссылка на подшаблон

Если быть более точным, комбинация «\cx» интерпретируется следующим образом: если «x» — символ нижнего регистра, он преобразовывается в верхний регистр. После этого шестой бит символа (шестнадцатеричный код 40) инвертируется. Таким образом, комбинация «\cz» интерпретируется как шестнадцатеричное значение 1A, тогда как комбинация «\c{» получает шестнадцатеричное значение 3B, а «\c;» — 7B.

После комбинации «\x» считываются ещё две шестнадцатеричные цифры в нижнем или верхнем регистре. В режиме UTF-8 разрешена запись «\x{...}», где содержимое скобок — строка из шестнадцатеричных цифр. Такая запись интерпретируется как символ UTF-8, кодовый номер которого — шестнадцатеричное число. Исходная шестнадцатеричная экранирующая последовательность \xhh совпадает с двухбайтовым символом в кодировке UTF-8, если значение шестнадцатеричного числа превышает 127.

После комбинации «\0» считываются ещё две восьмеричные цифры. Если в записи менее двух цифр, считываются только те, которые указали. Таким образом, последовательность «\0\x\07» будет интерпретирована как два двоичных нуля, за которыми следует символ оповещения (звонок). Убедитесь, что указали две цифры после начального нуля, если символ, который идёт за нулём, сам восьмеричная цифра.

Обработка обратного слеша, за которым следует ненулевая цифра, несколько сложнее. Вне символьного класса модуль PCRE воспринимает обратный слеш и цифры за ним как десятичное число. Если полученное значение меньше десяти или если шаблон содержит по меньшей мере такое же количество предшествующих текущей позиции подшаблонов, парсер интерпретирует всю конструкцию как ссылку на подшаблон. Более детальное описание приведём ниже при обсуждении механизма работы подшаблонов.

Внутри символьного класса, либо если полученное значение больше 9 и соответствующее количество предшествующих подшаблонов отсутствует, модуль PCRE считывает до трёх восьмеричных цифр, которые следуют за обратным слешем, и генерирует один байт из последних 8-ми значащих битов полученного значения. Все последующие цифры обозначают сами себя. Например:

\040
ещё один способ записи пробела
\40
то же самое, если данной записи предшествует менее сорока подшаблонов
\7
всегда интерпретируется как ссылка на подшаблон
\11
может быть как обратной ссылкой, так и альтернативной записью символа табуляции
\011
всегда интерпретируется как символ табуляции
\0113
символ табуляции, за которым следует цифра «3»
\113
интерпретируется как символ с восьмеричным кодом 113 (так как ссылок на подшаблоны не может быть больше 99)
\377
байт, всецело состоящий из единичных битов
\81
либо обратная ссылка, либо бинарный ноль, за которым следуют цифры «8» и «1»

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

Все последовательности, определяющие однобайтное значение, могут встречаться как внутри, так и вне символьных классов. Кроме того, внутри символьного класса запись «\b» интерпретируется как символ возврата («backspace», шестнадцатеричный код 08). Вне символьного класса она имеет другое значение (какое именно, описано ниже).

Третье использование обратного слеша — указание общего типа символов:

\d
любая десятичная цифра
\D
любой символ, кроме десятичной цифры
\h
любой горизонтальный пробельный символ
\H
любой символ, не являющийся горизонтальным пробельным символом
\s
любой пробельный символ
\S
любой непробельный символ
\v
любой вертикальный пробельный символ
\V
любой символ, не являющийся вертикальным пробельным символом
\w
Любой символ, образующий «слово»
\W
Любой символ, не образующий «слово»

Каждая пара таких специальных последовательностей делит полное множество всех символов на два непересекающихся множества. Любой символ соответствует одному и только одному множеству из пары.

Следующие символы считаются «пробельными»: HT (9), LF (10), FF (12), CR (13) и пробел (32). Однако, если идёт сопоставление с учётом локали, символы с кодовыми точками в диапазоне 128-255 также рассматриваются как пробельные, например, символ неразрывного пробела NBSP (A0).

Символ, образующий «слово» — это произвольная цифра, буква или символ подчёркивания, проще говоря, любой символ, которому разрешено быть частью «слова» в Perl. Определение букв и цифр управляется символьными таблицами, с которыми собрали модуль PCRE. И, как следствие, эти наборы могут отличаться в различных локализированных дистрибутивах. Например, в локали «fr» (Франция) ряд символов с кодом выше 128 применяют для записи ударных символов и поэтому они соответствуют маске \w.

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

Четвёртое назначение обратного слеша — определение некоторых формальных утверждений, которые описывают условия месторасположения особых позиций в строке и не затрагивают сами символы. Работу с подшаблонами как с более сложными формальными утверждениями, документация описывает ниже. К такими управляющим последовательностям относятся:

\b
граница слова
\B
не является границей слова
\A
начало данных (независимо от многострочного режима)
\Z
конец данных либо позиция перед последним переводом строки (независимо от многострочного режима)
\z
конец данных (независимо от многострочного режима)
\G
первая совпадающая позиция в строке

Описанные выше последовательности не могут встречаться в символьных классах (исключая комбинацию «\b», которая внутри класса означает символ возврата «backspace»).

Границей слова считается такая позиция в строке, в которой из текущего и предыдущего символа только один соответствует \w или \W (т.е. один из них соответствует \w, а другой \W). Начало или конец строки также соответствуют границе слова Если первый или, соответственно, последний символ совпадает с \w.

Утверждения \A, \Z и \z отличаются от традиционных метасимволов начала и конца строки строки — циркумфлекса ^ и знака доллара $, которые описывает раздел «Якоря», тем, что они всегда совпадают только в самом начале и конце входной строки, какие бы параметры ни установили. На них никак не влияют опции PCRE_MULTILINE и PCRE_DOLLAR_ENDONLY. Разница между \Z и \z в том, что \Z соответствует позиции перед последним символом, если последний символ — перевод строки, кроме самого конца строки. Тогда как \z соответствует исключительно концу данных.

Утверждение \G будет истинным, только если текущая проверяемая позиция находится в начале совпадения, которое указали в параметре offset функции preg_match(). Она отличается от последовательности \A при ненулевом значении параметра offset.

Последовательности \Q и \E игнорируют метасимволы регулярного выражения в шаблоне. Например: шаблон \w+\Q.$.\E$ совпадёт с «заякоренными» в конце строки одним или более символами слова, за которыми следуют литералы .$.. Обратите внимание, что это не меняет поведение разделителей; например, шаблон #\Q#\E#$ неправильный, потому что второй символ # отмечает конец шаблона, а последовательность \E# парсер интерпретирует как недопустимые модификаторы.

Последовательность \K сбрасывает начало совпадения. Например, шаблон foo\Kbar совпадёт со значением «foobar», но сообщит, что совпал только с «bar». Последовательность \K не мешает установке подшаблонов. Например, если шаблон (foo)\Kbar совпадёт со строкой «foobar», первым подшаблоном всё равно будет значение «foo».

add a note

User Contributed Notes 6 notes

up
52
mike at eastghost dot com
12 years ago
"line break" is ill-defined:

-- Windows uses CR+LF (\r\n)
-- Linux LF (\n)
-- OSX CR (\r)

Little-known special character:
\R in preg_* matches all three.

preg_match( '/^\R$/', "match\nany\\n\rline\r\nending\r" ); // match any line endings
up
10
Wirek
6 years ago
Significantly updated version (with new $pat4 utilising \R properly, its results and comments):
Note that there are (sometimes difficult to grasp at first glance) nuances of meaning and application of escape sequences like \r, \R and \v - none of them is perfect in all situations, but they are quite useful nevertheless. Some official PCRE control options and their changes come in handy too - unfortunately neither (*ANYCRLF), (*ANY) nor (*CRLF) is documented here on php.net at the moment (although they seem to be available for over 10 years and 5 months now), but they are described on Wikipedia ("Newline/linebreak options" at https://en.wikipedia.org/wiki/Perl_Compatible_Regular_Expressions) and official PCRE library site ("Newline convention" at http://www.pcre.org/original/doc/html/pcresyntax.html#SEC17) pretty well. The functionality of \R appears somehow disappointing (with default configuration of compile time option) according to php.net as well as official description ("Newline sequences" at https://www.pcre.org/original/doc/html/pcrepattern.html#newlineseq) when used improperly.
A hint for those of you who are trying to fight off (or work around at least) the problem of matching a pattern correctly at the end ($) of any line in multiple lines mode (/m).
<?php
// Various OS-es have various end line (a.k.a line break) chars:
// - Windows uses CR+LF (\r\n);
// - Linux LF (\n);
// - OSX CR (\r).
// And that's why single dollar meta assertion ($) sometimes fails with multiline modifier (/m) mode - possible bug in PHP 5.3.8 or just a "feature"(?).
$str="ABC ABC\n\n123 123\r\ndef def\rnop nop\r\n890 890\nQRS QRS\r\r~-_ ~-_";
// C 3 p 0 _
$pat1='/\w$/mi'; // This works excellent in JavaScript (Firefox 7.0.1+)
$pat2='/\w\r?$/mi'; // Slightly better
$pat3='/\w\R?$/mi'; // Somehow disappointing according to php.net and pcre.org when used improperly
$pat4='/\w(?=\R)/i'; // Much better with allowed lookahead assertion (just to detect without capture) without multiline (/m) mode; note that with alternative for end of string ((?=\R|$)) it would grab all 7 elements as expected
$pat5='/\w\v?$/mi';
$pat6='/(*ANYCRLF)\w$/mi'; // Excellent but undocumented on php.net at the moment (described on pcre.org and en.wikipedia.org)
$n=preg_match_all($pat1, $str, $m1);
$o=preg_match_all($pat2, $str, $m2);
$p=preg_match_all($pat3, $str, $m3);
$r=preg_match_all($pat4, $str, $m4);
$s=preg_match_all($pat5, $str, $m5);
$t=preg_match_all($pat6, $str, $m6);
echo
$str."\n1 !!! $pat1 ($n): ".print_r($m1[0], true)
.
"\n2 !!! $pat2 ($o): ".print_r($m2[0], true)
.
"\n3 !!! $pat3 ($p): ".print_r($m3[0], true)
.
"\n4 !!! $pat4 ($r): ".print_r($m4[0], true)
.
"\n5 !!! $pat5 ($s): ".print_r($m5[0], true)
.
"\n6 !!! $pat6 ($t): ".print_r($m6[0], true);
// Note the difference among the three very helpful escape sequences in $pat2 (\r), $pat3 and $pat4 (\R), $pat5 (\v) and altered newline option in $pat6 ((*ANYCRLF)) - for some applications at least.

/* The code above results in the following output:
ABC ABC

123 123
def def
nop nop
890 890
QRS QRS

~-_ ~-_
1 !!! /\w$/mi (3): Array
(
[0] => C
[1] => 0
[2] => _
)

2 !!! /\w\r?$/mi (5): Array
(
[0] => C
[1] => 3
[2] => p
[3] => 0
[4] => _
)

3 !!! /\w\R?$/mi (5): Array
(
[0] => C

[1] => 3
[2] => p
[3] => 0
[4] => _
)

4 !!! /\w(?=\R)/i (6): Array
(
[0] => C
[1] => 3
[2] => f
[3] => p
[4] => 0
[5] => S
)

5 !!! /\w\v?$/mi (5): Array
(
[0] => C

[1] => 3
[2] => p
[3] => 0
[4] => _
)

6 !!! /(*ANYCRLF)\w$/mi (7): Array
(
[0] => C
[1] => 3
[2] => f
[3] => p
[4] => 0
[5] => S
[6] => _
)
*/
?>
Unfortunately, I haven't got any access to a server with the latest PHP version - my local PHP is 5.3.8 and my public host's PHP is version 5.2.17.
up
10
grigor at the domain gatchev.info
13 years ago
As \v matches both single char line ends (CR, LF) and double char (CR+LF, LF+CR), it is not a fixed length atom (eg. is not allowed in lookbehind assertions).
up
3
Anonymous
4 years ago
A non breaking space is not considered as a space and cannot be caught by \s.

it can be found with :

- [\xc2\xa0] in utf-8
- \x{00a0} in unicode
up
-1
tharabar at gmail dot com
4 years ago
Required to use \007 instead of \a
up
-3
Wirek
6 years ago
Note that there are (sometimes difficult to grasp at first glance) nuances of meaning and application of escape sequences like \r, \R and \v - none of them is perfect in all situations, but they are quite useful nevertheless. Some official PCRE control options and their changes come in handy too - unfortunately neither (*ANYCRLF), (*ANY) nor (*CRLF) is documented here on php.net at the moment (although they seem to be available for over 10 years and 5 months now), but they are described on Wikipedia ("Newline/linebreak options" at https://en.wikipedia.org/wiki/Perl_Compatible_Regular_Expressions) and official PCRE library site ("Newline convention" at http://www.pcre.org/original/doc/html/pcresyntax.html#SEC17) pretty well. The functionality of \R appears somehow disappointing (with default configuration of compile time option) according to php.net as well as official description ("Newline sequences" at https://www.pcre.org/original/doc/html/pcrepattern.html#newlineseq).

A hint for those of you who are trying to fight off (or work around at least) the problem of matching a pattern correctly at the end ($) of any line in multiple lines mode (/m).
<?php
// Various OS-es have various end line (a.k.a line break) chars:
// - Windows uses CR+LF (\r\n);
// - Linux LF (\n);
// - OSX CR (\r).
// And that's why single dollar meta assertion ($) sometimes fails with multiline modifier (/m) mode - possible bug in PHP 5.3.8 or just a "feature"(?).
$str="ABC ABC\n\n123 123\r\ndef def\rnop nop\r\n890 890\nQRS QRS\r\r~-_ ~-_";
// C 3 p 0 _
$pat1='/\w$/mi'; // This works excellent in JavaScript (Firefox 7.0.1+)
$pat2='/\w\r?$/mi';
$pat3='/\w\R?$/mi'; // Somehow disappointing according to php.net and pcre.org
$pat4='/\w\v?$/mi';
$pat5='/(*ANYCRLF)\w$/mi'; // Excellent but undocumented on php.net at the moment
$n=preg_match_all($pat1, $str, $m1);
$o=preg_match_all($pat2, $str, $m2);
$p=preg_match_all($pat3, $str, $m3);
$r=preg_match_all($pat4, $str, $m4);
$s=preg_match_all($pat5, $str, $m5);
echo
$str."\n1 !!! $pat1 ($n): ".print_r($m1[0], true)
.
"\n2 !!! $pat2 ($o): ".print_r($m2[0], true)
.
"\n3 !!! $pat3 ($p): ".print_r($m3[0], true)
.
"\n4 !!! $pat4 ($r): ".print_r($m4[0], true)
.
"\n5 !!! $pat5 ($s): ".print_r($m5[0], true);
// Note the difference among the three very helpful escape sequences in $pat2 (\r), $pat3 (\R), $pat4 (\v) and altered newline option in $pat5 ((*ANYCRLF)) - for some applications at least.

/* The code above results in the following output:
ABC ABC

123 123
def def
nop nop
890 890
QRS QRS

~-_ ~-_
1 !!! /\w$/mi (3): Array
(
[0] => C
[1] => 0
[2] => _
)

2 !!! /\w\r?$/mi (5): Array
(
[0] => C
[1] => 3
[2] => p
[3] => 0
[4] => _
)

3 !!! /\w\R?$/mi (5): Array
(
[0] => C

[1] => 3
[2] => p
[3] => 0
[4] => _
)

4 !!! /\w\v?$/mi (5): Array
(
[0] => C

[1] => 3
[2] => p
[3] => 0
[4] => _
)

5 !!! /(*ANYCRLF)\w$/mi (7): Array
(
[0] => C
[1] => 3
[2] => f
[3] => p
[4] => 0
[5] => S
[6] => _
)
*/
?>
Unfortunately, I haven't got any access to a server with the latest PHP version - my local PHP is 5.3.8 and my public host's PHP is version 5.2.17.
To Top