Функция preg_replace

Существует несколько функций PHP для работы с регулярными выражениями. Мы начнем знакомится с ними на примере preg_replace: эта функция очень похожа на str_replace – тоже осуществляет поиск и замену, только первым параметром принимается не просто строка, а регулярное выражение. Посмотрите разницу:

<?php
	str_replace(что меняем, на что меняем, где меняем)
	preg_replace(что меняем, на что меняем, где меняем)

	echo str_replace('a', '!', 'aabbaa'); //выведет '!!bb!!'
	echo preg_replace('#a#', '!', 'aabbaa'); //выведет '!!bb!!'
?>

Обратите внимание на решетки #, в которых стоит буква 'a'. Эти решетки называются ограничителями регулярных выражений.

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

<?php
	echo preg_replace('#A#', '!', 'aAb'); //выведет 'a!b'
	echo preg_replace('#A#i', '!', 'aAb'); //выведет '!!b'

	//Во втором случае игнорируется регистр символов.
?>

Буквы, цифры, любой символ

В регулярных выражениях есть два типа символов: те, которые обозначают сами себя и символы-команды (специальные символы).

Буквы и цифры обозначают сами себя, а вот точка является специальным символом и обозначает 'любой символ'. Смотрите примеры:

<?php
	echo preg_replace('#xax#', '!', 'xax xaax'); //выведет '! xaax'
	echo preg_replace('#123#', '!', '123 xaax'); //выведет '! xaax'
	echo preg_replace('#x3x#', '!', 'x3x xaax'); //выведет '! xaax'

	//Обратите внимание на то, что регистр имеет значение:
	echo preg_replace('#A3B#', '!', 'a3b A3B'); //выведет 'a3b !'
?>

В принципе, сейчас нет разницы между preg_replace и str_replace – они работают одинаково, разница лишь в ограничителях (для регулярок они нужны).

Далее посмотрите примеры с использованием спецсимвола 'точка' (такое на str_replace уже не сделать):

<?php
	echo preg_replace('#x.x#', '!', 'xax xsx x&x x-x xaax'); //выведет '! ! ! ! xaax'
?>

Так как точка - это любой символ, то под нашу регулярку попадут все подстроки по такому шаблону: буква 'x', затем любой символ, затем опять буква 'x'. Первые 4 подстроки попали под этот шаблон (это xax xsx x&x x-x) и заменились на '!', а последняя подстрока (xaax) не попадает, так как внутри (между буквами 'x') не один символ - а целых два.

<?php
	echo preg_replace('#x..x#', '!', 'xax xabx'); //выведет 'xax !'
?>

Так как точка - это любой символ, а в нашей регулярке идут две точки подряд, то под нашу регулярку попадут все подстроки по такому шаблону: буква 'x', затем два любых символа, затем опять буква 'x'. Первая подстрока не попала под этот шаблон (так как у нее только 1 символ между буквами 'x'), а последняя подстрока (xabx) - попала.

Итак, запомните: буквы и цифры обозначают сами себя, а точка заменяет любой символ.

Внимание: для preg_match точка на самом деле обозначает любой символ, кроме перевода строки. Чтобы точка обозначала и его - нужно поставить модификатор s. См. главу №4 по регулярным выражениям.

Операторы повторения символов (*,+,?)

Бывают ситуации, когда мы хотим указать, что символ повторяется заданное количество раз. Если мы знаем точное число повторений, то можно просто написать его несколько раз - ('#aaaa#'). Но что делать, если мы хотим сказать такое: 'повторить один или более раз'?

Для этого существуют операторы (квантификаторы) повторения: плюс '+' (один и более раз), звездочка '*' (ноль и более раз) и вопрос '?' (ноль или один раз, иначе говоря - может быть, а может не быть).

Данные операторы действуют на тот символ, который стоит перед ними.

Посмотрите примеры:

<?php
	echo preg_replace('#xa+x#', '!', 'xx xax xaax xaaax xbx'); //выведет 'xx ! ! ! xbx'
?>

В данном случае шаблон поиска выглядит так: буква 'x', буква 'a' один или более раз, буква 'x'.

<?php
	echo preg_replace('#xa*x#', '!', 'xx xax xaax xaaax xbx'); //выведет '! ! ! ! xbx'
?>

В данном случае шаблон поиска выглядит так: буква 'x', буква 'a' ноль или более раз, буква 'x'. По-другому можно сказать так: буквы 'a' или нет, или повторяется один или более раз.

Кроме очевидного варианта xax xaax xaaax, под шаблон также попадает подстрока 'xx', так как там нет буквы 'a' вообще (то есть 0 раз).

А 'xbx' не попал, так как там нет буквы 'a', но есть буква 'b' (ее мы не разрешили).

<?php
	echo preg_replace('#xa?x#', '!', 'xx xax xaax xbx'); //выведет '! ! xaax xbx'
?>

В данном случае шаблон поиска выглядит так: буква 'x', далее буква 'a' может быть или не быть, потом буква 'x'.

Группирующие скобки

В предыдущих примерах операторы повторения действовали только на один символ, который стоял перед ними. Что делать, если мы хотим подействовать им на несколько символов?

Для этого существуют группирующие скобки '(' и ')':

<?php
	echo preg_replace('#x(ab)+x#', '!', 'xabx xababx xaabbx'); //выведет '! ! xaabbx'
?>

В данном случае шаблон поиска выглядит так: буква 'x', далее строка 'ab' один или более раз, потом буква 'x'.

То есть: если что-то стоит в группирующих скобках и сразу после ')' стоит оператор повторения - он подействует на все, что стоит внутри скобок.

Экранировка спецсимволов

Предположим, что мы хотим сделать так, чтобы спецсимвол обозначал сам себя. Например, для того, чтобы найти по такому шаблону: буква 'a', затем плюс '+', затем буква 'x'. Следующий код будет работать не так, как хотелось бы:

<?php
	echo preg_replace('#a+x#', '!', 'a+x ax aax aaax'); //выведет 'a+x ! ! !''
?>

Автор регулярки хотел, чтобы шаблон поиска выглядел так: буква 'a', затем плюс '+', затем буква 'x'.

А на самом деле он выглядит так: буква 'a' один или более раз, потом буква 'x'.

Поэтому подстрока 'a+x' и не попала под шаблон (мешает '+') - а все остальные попали.

Итак, правило: чтобы спецсимвол обозначал сам себя - его нужно экранировать с помощью обратного слеша. Вот так:

<?php
	echo preg_replace('#a\+x#', '!', 'a+x ax aax aaax'); //выведет 'a+x ! ! !'
?>

Вот теперь шаблон поиска выглядит так, как надо: буква 'a', затем плюс '+', затем буква 'x'.

<?php
	echo preg_replace('#a\.x#', '!', 'a.x abx azx'); //выведет '! abx azx'
?>

В данном примере шаблон выглядит так: буква 'a', затем точка '.', затем буква 'x'. Сравните со следующим примером (забыт обратный слеш):

<?php
	echo preg_replace('#a.x#', '!', 'a.x abx azx'); //выведет '! ! !'
?>

Все подстроки попали под шаблон, так как незаэкранированная точка обозначает любой символ.

Обратите внимание на то, что если вы забудете обратный слеш для точки (когда она должна обозначать сама себя) - этого можно даже не заметить:

<?php
	echo preg_replace('#a.x#', '!', 'a.x'); //выведет '!', как мы и хотели
?>

Визуально работает правильно (так как точка обозначает любой символ, в том числе и обычную точку '.'). Но если поменять строку, в которой происходят замены - мы увидим нашу ошибку:

<?php
	echo preg_replace('#a.x#', '!', 'a.x abx azx'); //выведет '! ! !', а ожидалось '! abx azx'
?>

Будьте внимательны!

Ограничители

В качестве ограничителей могут выступать не только #, но и любые другие символы (только не буквы и не цифры). Если используются скобки, тогда левый ограничитель - это открывающая скобка, а правый - закрывающая:

<?php
	echo preg_replace('&a+&', '!', 'строка'); //ограничители - амперсанды
?>
<?php
	echo preg_replace('(a+)', '!', 'строка'); //ограничители - скобки
?>

Если символ не является специальным, то когда вы используете его в качестве ограничителя, его нужно будет экранировать в самой регулярке (он станет специальным):

<?php
	//Ограничители - решетки, амперсанд не экранируем:
	echo preg_replace('#a&b#', '!', 'a&b'); //выведет '!'
?>
<?php
	/*
		Ограничители - амперсанды, 
		и теперь амперсанд внутри приходится экранировать,
		иначе это вызовет ошибку PHP:
	*/
	echo preg_replace('&a\&b&', '!', 'a&b'); //выведет '!'
?>

Список специальных символов и обычных

Если экранировать обычный символ - ничего страшного не случится - он все равно будет обозначать сам себя.

Исключение - цифры, они станут карманами, см. главу №3 по регулярным выражениям.

Еще исключение - модификатор 'X': если он установлен, то экранировка обычных символов вызовет ошибку, см. главу №4 по регулярным выражениям.

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

Являются спецсимволами: $ ^ . * + ? \ {} [] () |

Не являются спецсимволами: @ : , ' '' ; - _ = < > % # ~ `& ! /

Кроме того: спецсимволами будут выбранные ограничители.

И еще: # будет спецсимволом при наличии модификатора 'x' (именно маленький 'x' – он разрешает комментарии в регулярке).

Ограничение жадности

Чтобы понять, о чем пойдет речь - посмотрите пример:

<?php
	//Выведет '! e', а ожидалось '! qw x e':
	echo preg_replace('#a.+x#', '!', 'a23e4x qw x e'); 
?>

В данном примере шаблон поиска выглядит так: буква 'a', затем любой символ один или более раз, затем буква 'x'.

Однако, регулярка сработала не так, как ожидал автор - она захватила максимально возможное количество символов, то есть закончилась не на первом иксе 'x', а на последнем 'x'.

Такое поведение операторов (квантификаторов) повторения называется жадностью - они стремятся забрать как можно больше.

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

Делается это с помощью добавления знака '?' к оператору повторения: вместо жадных '+' и '*' мы напишем '+?' и '*?' - они будут не такие жадные:

<?php
	echo preg_replace('#a.+?x#', '!', 'a23e4x qw x e'); выведет '! qw x e'
?>

В данном примере шаблон поиска выглядит так: буква 'a', затем любой символ один или более раз (с ограничением жадности), затем буква 'x'.

С помощью '?' мы ограничили жадность плюсу - и теперь он ищет до первого совпадения.

Жадность можно ограничивать всем операторам повторения, в том числе и '?', и '{}' - вот так: '??' и '{}?'.