Pers.narod.ru. PHP. Регулярні вирази

  1. Регулярні вирази - це просто
  2. Символьні класи та спеціальні символи
  3. квантіфікатори
  4. Стандартні функції для роботи з регулярними виразами
  5. Жадібність і лінь
  6. позиційні перевірки
  7. Висновок

Pers.narod.ru . PHP . Регулярні вирази - це просто

Регулярні вирази - це просто

Вступ

Попросту кажучи, регулярні вирази (regular expressions, скорочено regexp, regex) - це шаблони, які можна зіставляти з рядками. На відміну від звичайного визначення входження підрядка в рядок, яке в PHP можна робити, наприклад, з допомогою функції strpos, шаблони дозволяють включати в шукане або заменяемое строкове вираження групові символи (wildcards) - саме в цьому їх сила.

Регулярні вирази доступні не тільки в PHP, але і в більшості сучасних мов і пакетів, які працюють з текстом - від Perl, Javascript і .NET Framework до Microsoft Office, так що знати їх корисно будь-якому грамотному користувачеві, не кажучи вже про програмістів. Однак, початківців regexp'и нерідко лякають, а товстелезні керівництва наводять тугу (наприклад, сама класична і рекомендована книга - Mastering Regular Expressions автора Jeffrey EF).

Тому постараємося сказати про все гранично коротко і доступно.

У PHP є 2 типу регулярних виразів:

  • POSIX-сумісні (POSIX означає всього лише Portable Operating System Interface for Unix) - в PHP під Windows доступні за замовчуванням, під Unix можна встановити під час налаштування конфігурації PHP за допомогою ключа --with-regex
  • Perl-сумісні або PCRE (Perl Compatible Regular Expressions) - мають синтаксичні відмінності, але вважаються більш швидкими і потужними. Під Windows і Unix регулярні вирази PCRE доступні за замовчуванням, саме на них ми будемо орієнтуватися надалі.

Символьні класи та спеціальні символи

Спочатку опишемо основні спеціальні символи (символьні класи) регулярних виразів, навівши приклади по кожному з них.

^
Початок рядка. Ставиться перед символом або шаблоном, на який діє.
^ А - рядок починається з "а"

$
Кінець рядка. Ставиться після символу або шаблону, на який діє.
а $ - рядок закінчується на "а"

[]
У квадратних дужках вказуються альтернативні символи, в рядку може бути присутнім будь-який з них.
PHP [345] може означати "PHP3", "PHP4" або "PHP5"

- всередині квадратних дужок
Символом "мінус" (дефіс) поділяються перший і останній символи послідовності, в рядку може бути присутнім будь-який символ від першого до останнього включно. Зрозуміло, "послідовності символів" мають сенс лише щодо поточного кодування. Наприклад, в UTF-8 (Юникоде) і Windows-1251 (стандартна русифікована Windows) російські букви (крім "е") гарантовано мають йдуть підряд коди, в кодуваннях DOS і КОИ-8R це не так.
PHP [3-5] може означати "PHP3", "PHP4" або "PHP5"

^ Всередині квадратних дужок
У рядку допустимо будь-які символи, крім зазначених цим знаком.
[^ AC] означає будь-яку цифру, або букву від "D" і далі за алфавітом і т.п.

|
Використовується як роздільник для кількох альтернативних шаблонів.
PHP4 | PHP5 означає "PHP4" або "PHP5"

()
Круглі дужки визначають подшаблон, незалежну складову частину основного шаблону. Корисно окремі "частини" строкового формату визначати як окремі подшаблони.
(А) (б) означає "аб", але як 2 подшаблона, а й б

.
Точка позначає будь-який символ.
PHP. може означати "PHP3", "PHP4", "PHP5" або "PHPA" і т.д.

\
Наступний після "\" символ не рахується спеціальним, іноді кажуть, що "\" екранує йде за ним символ.
\. означає символ точки, але не будь-який символ
\\ означає символ "\", але не керуючий символ

Перерахуємо ще кілька корисних спеціальних символів, які є в PCRE-сумісних регулярних виразах:
\ S - пробіл або табуляція або інший пробільний (space) символ;
\ S - видимі символи, можна вважати, що це все, що не збігається з \ s;
\ W - в цей спецсимвол включені всі символи, які можуть входити в слово (word), зазвичай [a-zA-Z_], хоча це залежить від встановленої локалі, підтримки Юнікоду і т.п .;
\ W - все, що не входить у визначення \ w, тобто, [^ a-zA-Z_];
\ D - цифра (digit; спеціальний символ дозволяє не писати символьний клас [0-9]);
\ D - все, що не є цифрою.

квантіфікатори

Найважливіше - один символьний клас описує тільки один символ, а коли символів багато, застосовуються квантіфікатори.

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

[Az] [az] [0-9] [0-9]

А якщо ми не полінуємося вказати квантіфікатори, запис можна змінити:

[Az] {2} [0-9] {2}

А ось і самі квантіфікатори:

?
Нуль або одне повторення попереднього символу або виразу ( "нуль або один").
аб? може означати "а" або "аб"

*
Нуль або більше повторень попереднього символу або виразу ( "нуль або багато").
аб * може означати "а" або "аб" або "абб" і т.д.
Часто використовується послідовність. * Для позначення будь-якої кількості будь-яких символів між двома частинами регулярного виразу.

+
Одне або більше повторень попереднього символу або виразу ( "хоча б один", "один або багато").
аб + може означати "аб" або "абб" або "АБББ" і т.д.

{N}
Шукається рівно n входжень попереднього символу або шаблону.
[0-9] {3} - три будь-яких йдуть підряд цифри

{Min, max}
Така конструкція в фігурних дужках позначає мінімальний (min) і максимальне (max) число входжень, якщо опущений мінімум min (але не кома), він передбачається рівним 0, якщо опущений максимум max (але не кома), він передбачається рівним infinite (нескінченності) .
a {1,3} може означати "а" або "аа" або "ааа"
a {1,} може означати ланцюжок з літер "а" будь-якої довжини, але не менше одного символу
a {, 3} може означати ланцюжок з літер "а" в кількості не більше трьох (зверніть увагу, нуль букв "а" теж потрапляє під цей вислів!)

Маніпулюючи зазначеними вище символьними класами, спеціальними символами і квантіфікаторамі, можна скласти багато корисних прикладів, скажімо, таких:
[^ \ S] - будь-який символ, який не є пробільним;
[^ \ S] + - мінімум один символ, який не є пробільним;
\ S + - мінімум один символ пробілу;
^ \ D + $ - рядок є числом з однієї і більше цифр (^ поза квадратних дужок - не виняток символів, а початок рядка; зверніть також увагу, що тут вся рядок повинна відповідати шаблоном - так як в нього включені мітки початку ^ і кінця $ рядки);
^ [A-zA-Z0-9] + $ - латинські букви і цифри, мінімум один символ;
^ [a-zа-я0-9 _] {1,8} $ - рядок тільки з латинських або російських букв, цифр і підкреслення від 1 до 8 символів довжиною (ігнорування регістра зазвичай можна вказати за допомогою описаних нижче модифікаторів шаблонів);
[^ (\ X7F- \ xFF) | (\ w) | (\ s)] - виключаємо символи з кодами 127 і більше (як бачите, в регулярних виразах можна писати 16-ковий коди символів в стилі мови Сі), дозволяємо друкуються і пробільні символи.

Стандартні функції для роботи з регулярними виразами

Як все це використовувати в PHP? Та дуже просто, нам знадобиться всього кілька стандартних функцій:

  • preg_match - виконує перевірку на відповідність рядка регулярному виразу;
  • preg_match_all - виконує глобальний пошук шаблону регулярного виразу в рядку;
  • preg_replace - виконує пошук і заміну по регулярному виразу;
  • preg_split - розбиває рядок на частини по регулярному виразу.

Подивимося докладніше на кожну з цих функцій.

int preg_match (string $ pattern, string $ subject [, array $ matches]);

Функція шукає в заданому тексті $ subject збіги з шаблоном $ pattern, якщо заданий необов'язковий третій параметр - масив $ matches, то пише в нього результати пошуку.

Елемент $ matches [0] буде містити частину рядка, відповідну входженню всього шаблону, $ matches [1] - частина рядка, відповідну першому подшаблону (якщо він є), і так далі.

Повертає кількість знайдених відповідностей. Це може бути 0 (збіги не знайдені) і 1, оскільки preg_match припиняє свою роботу після першого знайденого збіги.

Наведемо приклад, що вибирає з довільної рядки номер російського стільникового телефону виду + ​​7XXXXXXXXXX або 8XXXXXXXXXX, де X - цифри:

$ Number = 'Мій номер стільникового: +79169013311'; if (! preg_match ( "/ (\ + 7 | 8) (\ d {10}) /", $ number, $ matches)) echo 'В рядку'. $ number. ' не найден російський номер стільникового! '; else echo 'Знайдено номер'. $ matches [0];

В елементі $ matches [0] буде міститися весь знайдений номер, в $ matches [1] - перший подшаблон (+7 або 8), а в $ matches [2] - другий подшаблон (10 цифр стільникового номера).

Спробуйте поліпшити цей приклад, дозволивши між групами цифр шаблону прогалини або тире, так щоб перебувало +7 XXX XXX XX XX або 8-XXX-XX-XX-XXX.

Звернемо увагу на один нюанс: шаблон пошуку в першому аргументі функції не тільки взятий в "подвійні лапки", як будь-яка інтерпретується рядок PHP, але і поміщений в символи-обмежувачі / ... /, по-перше, ці обмежувачі потрібні функцій, щоб вони могли "зрозуміти" шаблон (крім / ... / використовують також # ... #), по-друге, це робиться тому, що після закриває символу / можуть ставитися так звані модифікатори шаблонів, уточнюючі їх дію:

  • i - ігнорувати регістр символів;
  • m - обробляти текст як багаторядковий;
  • s - метасимвол "точка" в шаблоні відповідає всім символам, включаючи переклад рядків (без модифікатора s - всім, за винятком переказів рядків);
  • x - ігнорувати неекрановані пробіли, символи табуляції і порожнього рядка в шаблоні;
  • e - тільки для preg_replace - після виконання стандартних підстановок в замінної рядку PHP інтерпретує її як код і використовує результат для заміни шуканої рядка;
  • u - включає сумісність з Unicode (UTF8).

Є ще кілька модифікаторів, але вони використовуються рідше.

Розглянемо інші функції, що застосовуються для пошуку в рядках по шаблонах regexp'ов.

int preg_match_all (string $ pattern, string $ subject, array $ & matches);

На відміну від попередньої функції, третій аргумент тут обов'язковий. Функція preg_match_all виконує глобальний пошук шаблону $ pattern в рядку $ subject і поміщає результати пошуку в масив $ matches.

Поверне ця функція кількість знайдених входжень шаблону (може бути нулем) або FALSE, якщо під час виконання виникли помилки. Після знаходження першого відповідності наступні пошуки будуть здійснюватися не з початку рядка, а від кінця останнього знайденого входження.

Щоб зрозуміти, як це працює, пошукаємо спочатку що-небудь просте, наприклад, цілі числа з допустимим, але необов'язковим знаком "+" або "-" перед першою цифрою.

$ Numbers = '11, -1, +1.5, -2e4 '; $ Res = preg_match_all ( "/ [\ + | -]? [\ D] + /", $ numbers, $ matches); if ($ res === false) echo 'Помилка!'; else if ($ res === 0) echo 'Нічого не знайдено'; else {for ($ i = 0; $ i <$ res; $ i ++) echo '<br>'. $ matches [0] [$ i]; }

Перше питання за нашим прикладом - чому ми виводили елементи $ matches [0] [$ i]? Справа в тому, що після того як знаходити елемент $ matches [0] містить масив повних входжень шаблону, елемент $ matches [1] містить масив входжень першого подшаблона, і так далі.

Таким чином, $ matches [0] [0] - це перше знайдене число, $ matches [0] [1] - друге і т.д.

Замість $ res можна було скористатися для визначення кількості знайдених чисел виразом count ($ matches [0]).

Друге питання - чому наш пошук "несподівано" знайшов в рядку $ numbers цілих 6 чисел?

11 -1 +1 5 -2 4

Справді, ми ж не вказали, що цифра повинна починатися після пробілу або з початку рядка?

Але писати шаблон на зразок "/ [\ + | -]? [\ D] + /" (розміщений пропуск перед знаком числа) не варто - втратимо число 11, яке є першим в рядку. Намагатися використовувати символ "^" (початок рядка) теж не будемо - тоді є ризик втратити всі інші числа.

Замість цього спробуємо виключити символи, яких безпосередньо перед цілим числом свідомо не може бути - бо вони є тільки в дійсних числах, наприклад, десяткову точку і букву e (використовується в так званій експоненційної запису дійсних чисел, де 2e4 означає 2 * 104): "/[^.eE][\+|-]?[\d]+/"

Такий шаблон знайде в рядку $ numbers рівно 4 числа:

11 -1 +1 -2

Легко помітити, що і цей шаблон недосконалий. Наприклад, в рядку

2E-4

(В експоненційної запису числа після E або e можна використовувати знак "-" або "+", що показує, на негативну або позитивну ступінь числа 10 потрібно помножити підставу) наш шаблон знайде 2 цифри: 2 і -4.

Можна і далі викручуватись в поліпшенні шаблону, але краще дотримуватися простий принцип - починати з початку, а не з кінця, як зробили ми тільки що.

Давайте перевернемо завдання з голови на ноги. У нас є рядки формату [знак] [цифри] [інше], нам потрібно залишити тільки [знак] [цифри].

Винесемо [інше] в окремий подшаблон, і будемо друкувати не $ matches [0] [$ i], а $ matches [1] [$ i]. Частина [знак] [цифри] легко опишеться подшаблоном ([\ + | -]? [\ D] +), а частина [інше] може мати вигляд ([^ \ s,] *) (будь-які символи, за винятком пробілів і коми - останнім на випадок, якщо 2 цілих числа розділені коми без пробілу: 1,2).

Отримуємо наступний код:

$ Numbers = '33 .01e-2 21.5E-004 11e + 01, -1,33, +1.5, -2e4 '; $ Res = preg_match_all ( "/ ([\ + | -]? [\ D] +) ([^ \ s,] *) /", $ numbers, $ matches); if ($ res === false) echo 'Помилка!'; else if ($ res === 0) echo 'Нічого не знайдено'; else {for ($ i = 0; $ i <$ res; $ i ++) echo '<br>'. $ matches [1] [$ i]; }

Він знайде те, що ми маємо право і очікувати для рядка $ numbers:

33 21 11 -1 33 +1 -2

Поведінка функції preg_match_all можна змінити, якщо передати їй четвертим параметром константу PREG_SET_ORDER. Тоді $ matches [0] буде містити перший набір входжень, $ matches [1] - другий набір входжень, і т.д.

Тобто, елемент $ matches [0] [0] містить перше входження всього шаблону, елемент $ matches [0] [1] містить перше входження першої подмаскі, і т.д. У нашому циклі зміниться лише порядок звернення до елементів масиву $ matches:

$ Numbers = '11 .006E + 014, -1.473, +1.5, -2e4 '; $ Res = preg_match_all ( "/ ([\ + | -]? [\ D] +) ([^ \ s,] *) /", $ numbers, $ matches, PREG_SET_ORDER); if ($ res === false) echo 'Помилка!'; else if ($ res === 0) echo 'Нічого не знайдено'; else {for ($ i = 0; $ i <$ res; $ i ++) echo '<br>'. $ matches [$ i] [1]; }

Нарешті, вказавши замість PREG_SET_ORDER прапор PREG_OFFSET_CAPTURE, ми могли для кожної знайденої підрядка повертати її позицію в заданій стрічці.

Цей прапор повністю змінить формат повертаються даних: кожне входження повертається у вигляді масиву, в нульовому елементі якого міститься знайдений підрядок, а в першому - зміщення.

Третя функція - preg_replace, дозволяє не тільки знайти, а й замінити рядки, які потрапляють під шаблони регулярних виразів.

string preg_replace (string $ pattern, string $ replacement, string $ subject);

Функція шукає в рядку $ subject збіги з шаблоном $ pattern і замінює їх на $ replacement.

При розборі цієї функції корисними виявляться подмаскі, відповідні ув'язненим в круглі дужки частинам шаблону (подшаблонам). Подмаскі послідовно нумеруються, починаючи з 1, подмаскі з номером 1 може позначатися в рядку шаблону як \\ 1 або як $ 1, причому останній варіант краще. Подмаскі \\ 0 або $ 0 відповідає знаходженню всього шаблону.

Якщо безпосередньо за подмаскі в рядку $ replacement повинна слідувати цифра, щоб уникнути плутанини подмаскі ізолюється фігурними дужками: \ $ {1} 0 відповідає першій подмаскі і наступної за нею цифрі 0, в іншому випадку \ $ 10 буде сприйнято як десята подмаскі.

Якщо під час виконання функції були виявлені збіги з шаблоном, буде повернуто змінений значення $ subject, в іншому випадку буде повернуто вихідний текст $ subject.

Наведемо приклад, в якому із запису чисел в експоненційною формі забирається "все зайве", тобто, попередні знаки "+" або "-", дрібна частина числа, якщо вона є, а також вся частина, починаючи з букви "e" або "E". Наш попередній шаблон для зручності ми перепишемо так, щоб він безпосередньо відбивав структуру числа в експоненційної формі:

  • ([\ + | \ -]?) - знак "+" або "-" перед числом, він же $ 1
  • (\ D +) - цифри цілої частини числа, $ 2
  • (\.?) - десяткова точка, $ 3
  • (\ D *) - цифри дробової частини, $ 4
  • ([E | E]?) - символ порядку "e" або "E", $ 5
  • ([\ + | \ -]?) - символ "+" або "-" перед значенням порядку, $ 6
  • (\ D *) - цифри порядку, $ 7

Тепер спробуємо підставити цей шаблон в невелику програмку (природно, прибравши з шаблону розриви рядків):

$ Numbers = '11 .07E-034, -1, +1.5006, -2e-11, 5 '; $ Res = preg_replace ( "/ ([\ + | \ -]?) (\ D +) (\.?) (\ D *) ([e | E]?) ([\ + | \ -]?) ( \ d *) / "," $ 2 ", $ numbers); if ($ res == $ numbers) echo 'Заміни не здійснено'; else echo $ res;

На екрані побачимо наступне:

11, 1, 1, 2, 5

Істотно, що параметри $ subject, $ pattern і $ replacement можуть бути масивами, в цьому випадку обробка проводиться для кожного з їх елементів.

Модифікатор / e змінює поведінку функції preg_replace так, що параметр $ replacement після виконання підстановок інтерпретується як PHP-код і тільки після цього використовується для заміни. Якщо запропонований код є коректним, виникне помилка синтаксису.

У функції є версія preg_replace_callback, при виклику якої замість параметра $ replacement програміст може вказати власну функцію, якої передається масив знайдених входжень.

Отже, круглі дужки дозволяють ще й запам'ятовувати подшаблони для подальшого використання в регулярному виразі. Якщо нам потрібно тільки групувати подшаблони, але не запам'ятовувати їх, заощадити ресурси комп'ютера можна за допомогою послідовності символів "?:" (Знак питання і двокрапка) після відкриває круглої дужки, яка дозволяє дужках НЕ запам'ятовувати:

(?: [0-9] *) ([az] *)

Тут перша пара круглих дужок - не запам'ятовується, так що код

$ Str = '0123abs dddeee 01aa'; $ Res = preg_replace ( "/ (?: [0-9] *) ([az] *) /", "$ 1", $ str); echo $ res;

покаже як $ 1 значення

abs dddeee aa

Четверта корисна стандартна функція - preg_split, вона розбиває рядок по регулярному виразу і повертає масив, що складається з подстрок:

array preg_split (string $ pattern, string $ subject [, int $ limit [, int $ flags]])

Як видно із загальної записи, у функції 2 обов'язкових параметра і 2 необов'язкових.

Параметр $ pattern як і раніше позначає регулярний вираз, а $ subject - рядок, з якої виробляється робота.

Параметр $ limit, якщо він заданий, показує максимальне число повертаються рядків. Якщо кількість рядків не обмежена, але потрібно вказати четвертий параметр ($ flags), передається $ limit = -1.

Параметр $ flags може бути довільною комбінацією наступних прапорів (з'єднання відбувається за допомогою оператора '|'):

  • PREG_SPLIT_NO_EMPTY - повертаті только непусті підрядка;
  • PREG_SPLIT_DELIM_CAPTURE - вирази, Укладення в круглі дужки в розділяє шаблоні, такоже вілучають Із заданого рядка и возвращается функцією;
  • PREG_SPLIT_OFFSET_CAPTURE - для кожної знайденої підрядка буде вказана ее позиція в заданій стрічці. Цей прапор змінює формат повертаються Даних: шкірні входження возвращается у виде масиву, в нульовий елементі которого містіться знайденій підрядок, а в Першому - зміщення.

Функція повертає масив, що складається з подстрок рядка $ subject, яка розбита на межі, відповідним шаблоном $ pattern.

Наприклад, для розбиття рядка на слова, розділені пробільними символами ( "\ r", "\ n", "\ t", "\ f", ""), досить наступного коду:

$ Text = "Це рядок тексту, що складається з слів, розділених пробілами або комами."; $ Words = preg_split ( "/ [\ s,] + /", $ text); for ($ i = 0; $ i <count ($ words); $ i ++) echo '<br>'. $ words [$ i];

Для посимвольного розбиття рядка можна було вчинити ще простіше:

$ Str = 'Рядок тексту'; $ Chars = preg_split ( '//', $ str, -1, PREG_SPLIT_NO_EMPTY); print_r ($ chars);

Тут стандартна функція print_r роздрукує масив в зручному для налагодження вигляді:

Array ([0] => З [1] => т [2] => р [3] => про [4] => до [5] => а [6] => [7] => т [ 8] => е [9] => до [10] => з [11] => т [12] => а)

Жадібність і лінь

Тепер трохи про жадібність і ліні, так-так, "жадібні" і "ледачі" квантіфікатори - цілком офіційні терміни.

Уявімо, що нам потрібно знайти в довільному тексті все теги HTML, ув'язнені в трикутні дужки. Шаблон тега буде дуже простим: "/<.*>/". Перевіримо, як це працює, на довільній рядку з тегами:

$ Html = '<p> Абзац <u> тексту </ u> з <b> різними </ b> тегами. </ P>'; $ Res = preg_match_all ( "/<.*>/",$ html, $ matches); if ($ res === false) echo 'Помилка!'; else if ($ res === 0) echo 'Нічого не знайдено'; else {echo '<br> Всього тегів:'. $ res; for ($ i = 0; $ i <$ res; $ i ++) echo '<br>'. htmlspecialchars ($ matches [0] [$ i]); }

На жаль, результатом видачі виявилася вся вихідна рядок:

Всього записів: 1 <p> Абзац <u> тексту </ u> з <b> різними </ b> тегами. </ P>

Так сталося тому, що ми не врахували можливі вкладення тегів, а за замовчуванням PHP шукає максимальну відповідність рядка шаблоном.

Така поведінка називається жадібним, зокрема, жадібність квантіфікатора "*" і привела до побаченого нами результату.

Крім жодних, існують і нежадібні або ліниві (lazy) квантіфікатори, які навпаки шукають мінімальне, найбільш короткий відповідність.

Щоб позначити квантіфікатор як ледачий, зазвичай досить додати після нього знак питання:

Жадібний Лінивий * *? + +? {N,} {n,}?

Як Ви розумієте, додавати додатковий питання до звичайного квантіфікатор "?", Що означає нуль або один символів, безглуздо.

З ледачим квантіфікатор "*" наш шаблон набуде вигляду "/<.*?>/" і програма благополучно надрукує наступне:

Всього записів: 6 <p> <u> </ u> <b> </ b> </ p>

Слід розуміти, що використання ледачих квантіфікаторов замість жодних може спричинити за собою зворотну проблему, зокрема, коли висловом відповідає занадто коротка або взагалі порожній рядок. Іноді має сенс замість "ліні" попрацювати над шаблоном, наприклад, в нашому випадку теги можна було знайти і звичайним жадібним квантіфікатор, застосувавши шаблон "/> [^>] *> /" (виключили символ ">" з "нутрощів" тега) .

Нарешті, існує ревнива або сверхжадная квантификация.

На відміну від звичайної (жадібної) квантификации, ревнива квантификация не тільки намагається знайти максимально довгий варіант, але ще й не дозволяє алгоритму повертатися до попередніх кроків пошуку для того, щоб знайти можливі відповідності для решти регулярного виразу.

Використання ревнивих квантіфікаторов збільшує швидкість пошуку, особливо в тих випадках, коли рядок не відповідає регулярному виразу. Крім того, ревниві квантіфікатори можуть бути використані для виключення небажаних збігів.

Ставити їх можна так:

Жадібний Ревнивий * * +? ? + + ++ {n,} {n,} +

позиційні перевірки

У синтаксисі регулярних виразів є можливість описати, що знаходиться до або після того ділянки рядки, який нам потрібно знайти. Критерій блокування, яке знаходиться перед шуканої підрядком, називають ретроспективної перевіркою, умова збігу, яке йде після підрядка, називається випереджаючої перевіркою. Інакше кажучи, ретроспективна перевірка збігається зліва, а випереджає - справа.

Щоб була можливість описати і те, чого не повинно бути перед шуканої підрядком або після неї, ретроспективну і випереджальну перевірки ділять ще на 2 види кожну:

  • ретроспективна позитивна і ретроспективна негативна перевірки;
  • випереджальна позитивна і випереджальна негативна перевірки.

У кожному разі позитивна перевірка описує те, що обов'язково має бути присутнім зліва (для ретроспективної) або праворуч (для випереджаючої) від шуканої підрядка.

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

Перевірки описуються в круглих дужках наступним чином:

  • Ретроспективна позитивна перевірка: (? <=)
  • Ретроспективна негативна перевірка: (? <!)
  • Випереджальна позитивна перевірка: (? =)
  • Випереджальна негативна перевірка: (?!)

Напевно, неважко відстежити логіку цих позначень: ретроспективность позначена знаком "<", немов відсилає наліво від рядка, позитивність - знаком "=", негативність - знаком "!", Що нагадує про операцію "не дорівнює" або заперечення в багатьох мовах програмування.

Знак питання після відкриває дужки як і раніше позначає, що дужки використовуються не для запам'ятовування, але лише для угруповання символів.

Наприклад, якщо в тексті частина цифр виділена тегом жирного накреслення <b> ... </ b>, а частина немає, нам же потрібно "витягнути" тільки цифри, виділені жирним, допомогти може ось такий шаблон:

(? <= \ <B \>) (? <! \ <\ / B \>) ([\ s] * [\ d] + [\ s] *)

Тут перед цифрами числа [\ d] +, оточеними будь-якою кількістю символів пробілів [\ s] *, ми вимагаємо наявності відкриває частини тега жирного шрифту (? <= \ <B \>) і відсутності його закриває частини (? <! \ < \ / b \>). Спробуємо застосувати це до якогось фрагменту розмітки HTML:

$ Text = "У цьому <b> тексті </ b> деякі цифри, наприклад, <b> 12 </ b>, виділені <b> жирним </ b>, а інші, наприклад, 13, немає. Нам потрібно \ "витягнути \" тільки ті, які виділені: <b> 14 </ b>, <i> 15 </ i>, <b> </ b> 16 </ b>. "; $ Res = preg_match_all ( "# (? <= \ <B \>) (? <! \ <\ / B \>) ([\ s] * [\ d] + [\ s] *) #", $ text, $ matches); if ($ res === false) echo 'Помилка!'; else if ($ res === 0) echo 'Нічого не знайдено'; else {for ($ i = 0; $ i <$ res; $ i ++) echo '<br>'. $ matches [0] [$ i]; }

Висновок програми:

12 14

Зайві пропуски перед та після числа 14 на заваді не стануть, а от врахувати запис вигляду <b> <i> 14 </ i> </ b> наш шаблон вже не зможе, і "розширення" ретроспективної позитивної перевірки на кшталт (? <= \ <b \> [.] *) тут не допоможуть, більш того, вони викличуть синтаксичну помилку. Справа в тому, що позиційні перевірки підкоряються ряду обмежень:

  • PCRE не дозволяє робити перевірки на збіг тексту довільної довжини. Тобто, (? <= \ D +) - неприпустимо. Зроблено це для того, щоб в разі розбіжності пошуковий механізм міг повернутися назад на фіксовану кількість символів і продовжити пошук збігів в інших позиційних перевірках.
  • Збіглися значення ретроспективних перевірок не зберігаються і не доступні через спеціальні змінні виду $ 1, $ 2 і т.д. Про це нагадує і знак питання після відкриває дужки.
  • Написані один за одним перевірки застосовуються незалежно один від одного в одній точці, не змінюючи її. Збіг буде знайдено, якщо всі перевірки співпадуть. З точки зору логіки немає ніякої різниці в порядку перевірок, але рекомендується ставити першої позиційної перевіркою ту, яка має найбільшу ймовірність розбіжності.

Висновок

Особливо хотілося б застерегти читача від спокуси використовувати "всемогутні" регулярні вирази по всіх усюдах, адже, крім маси достоїнств, вони мають і недоліки. Головний з них - свідомо більш повільна робота в порівнянні зі стандартними строковими функціями або зверненнями до елементів масивів. Наприклад, писати пошук по великій таблиці бази даних за допомогою регулярних виразів я б не став - набагато перспективніше виглядає в цьому плані інструкція LIKE з MySQL, що дозволяє шукати входження рядка в строкове поле таблиці.

Взагалі, якщо не потрібні складні правила пошуку і заміни рядків, використання preg_replace слід надавати перевагу str_replace, preg_split може бути замінена звичайної split, а з простими завданнями з пошуку і обробці фікірованних подстрок замість preg_match і preg_match_all успішно справляються strpos, strstr, substr, substr_replace і цілий ряд інших стандартних функцій, наявних в PHP.

Взагалі, якщо не потрібні складні правила пошуку і заміни рядків, використання preg_replace слід надавати перевагу str_replace, preg_split може бути замінена звичайної split, а з простими завданнями з пошуку і обробці фікірованних подстрок замість preg_match і preg_match_all успішно справляються strpos, strstr, substr, substr_replace і цілий ряд інших стандартних функцій, наявних в PHP

Аб?
Друге питання - чому наш пошук "несподівано" знайшов в рядку $ numbers цілих 6 чисел?
Справді, ми ж не вказали, що цифра повинна починатися після пробілу або з початку рядка?
Але писати шаблон на зразок "/ [\ + | -]?
EE][\+|-]?
E | E]?
E | E]?
N,} {n,}?
Як Ви розумієте, додавати додатковий питання до звичайного квантіфікатор "?