Регулярные выражения
BRE, ERE и PCRE на практике — классы, якоря, квантификаторы, группы, обратные ссылки, жадность
Регулярное выражение — шаблон, описывающий множество строк. Его понимают grep, sed, awk и многие другие программы, но в нескольких диалектах. Основа у всех общая, отличаются в основном спецсимволы, которые нужно (или не нужно) экранировать.
Три диалекта
- BRE (базовые) —
grepиsedбез опций. Символы+ ? | ( ) { }здесь обычные; спецсмысл им дают обратные слэши:\+ \? \| \( \) \{ \}(\+ \? \|— расширение GNU). - ERE (расширенные) —
grep -E,sed -E,awk. Те же+ ? | ( ) { }работают без слэшей, а\+и\(наоборот означают обычные символы. - PCRE (Perl) —
grep -P. Всё из ERE плюс\d \s \w, ленивые квантификаторы*?, просмотр(?=…)и(?<=…),\K, модификаторы(?i).
Одно и то же в трёх диалектах — «одна или больше цифр, потом точка»:
echo 'v12.3' | grep -o '[0-9]\+\.' # BRE
echo 'v12.3' | grep -oE '[0-9]+\.' # ERE
echo 'v12.3' | grep -oP '\d+\.' # PCRE
Символы и наборы
| Элемент | Что значит |
|---|---|
a, 1, - | обычный символ совпадает сам с собой |
. | любой один символ |
\. \* \[ \\ \$ \^ | спецсимвол как обычный |
[abc] | один символ из набора |
[^abc] | любой символ, кроме перечисленных |
[a-z] [0-9A-F] | диапазоны внутри набора |
[]a] / [a-] / [.*] | ] — первым, - — первым или последним; . и * внутри скобок — обычные символы |
Классы POSIX
Пишутся внутри квадратных скобок: [[:digit:]], [^[:space:]], [[:alpha:]_].
| Класс | Что входит |
|---|---|
[:digit:] / [:xdigit:] | цифры / шестнадцатеричные цифры |
[:alpha:] / [:alnum:] | буквы / буквы и цифры |
[:upper:] / [:lower:] | заглавные / строчные |
[:space:] / [:blank:] | все пробельные (пробел, \t, \n, \r …) / только пробел и табуляция |
[:punct:] | знаки препинания |
[:print:] / [:graph:] / [:cntrl:] | печатные с пробелом / печатные без пробела / управляющие |
Сокращения
| Элемент | Что значит | Где работает |
|---|---|---|
\w / \W | символ слова (буква, цифра, _) / не он | GNU grep, sed, gawk, PCRE |
\s / \S | пробельный / непробельный | GNU grep, sed, gawk, PCRE |
\d / \D | цифра / не цифра | только PCRE (grep -P) |
\t \n | табуляция, перевод строки | PCRE, gawk; в sed \n и \t — тоже |
Якоря
| Элемент | Что значит |
|---|---|
^ / $ | начало / конец строки |
\< / \> | начало / конец слова (GNU grep, sed, gawk) |
\b / \B | граница слова / не граница (GNU grep, sed, PCRE; в gawk граница — \y) |
Квантификаторы
Относятся к предыдущему символу, набору или группе.
| ERE / PCRE | BRE | Сколько раз |
|---|---|---|
* | * | 0 и больше |
+ | \+ | 1 и больше |
? | \? | 0 или 1 |
{3} | \{3\} | ровно 3 |
{2,5} | \{2,5\} | от 2 до 5 |
{2,} / {,5} | \{2,\} / \{,5\} | 2 и больше / не больше 5 |
Все квантификаторы жадные: берут как можно больше, а потом отступают, если иначе совпадение не найти. В PCRE есть ленивые *?, +?, ??, {2,5}? — они берут как можно меньше.
Группы и альтернатива
| Элемент | Что значит |
|---|---|
(…) (в BRE — \(…\)) | группа: квантификатор действует на всё содержимое, совпавший текст запоминается |
\1 … \9 | обратная ссылка — тот же текст, что совпал с группой. В шаблоне grep и sed, в замене sed; в awk их нет |
a|b | a или b; внутри группы — (jpg|png). В BRE перед чертой нужен обратный слэш |
(?:…) | группа без запоминания (PCRE) |
(?=…) / (?!…) | дальше идёт / не идёт … (PCRE, в совпадение не входит) |
(?<=…) / (?<!…) | перед этим стоит / не стоит … (PCRE) |
\K | забыть всё, что совпало до этого места (PCRE) |
(?i) | дальше без учёта регистра (PCRE) |
Примеры
# строка целиком из цифр
printf '123\n12a\n' | grep -E '^[0-9]+$'
# пустые строки и строки из одних пробелов
printf 'a\n\n \nb\n' | grep -c '^[[:space:]]*$'
# строки, где первый символ — не пробел и не #
printf '# comment\n indented\nvalue\n' | grep '^[^[:space:]#]'
# целое слово: cat, но не catalog
echo 'cat catalog' | grep -o '\<cat\>'
echo 'cat catalog' | grep -oE '\bcat\b'
# необязательная буква и точное число повторов
echo 'color colour' | grep -oE 'colou?r'
echo '2024-01-05' | grep -E '^[0-9]{4}-[0-9]{2}-[0-9]{2}$'
# альтернатива и группа с квантификатором
printf 'ab\nabab\naba\n' | grep -xE '(ab)+'
printf 'a.jpg\nb.png\nc.txt\n' | grep -E '\.(jpg|png)$'
# обратная ссылка: два одинаковых символа подряд
echo 'aa bc dd' | grep -oE '(.)\1'
# группы в замене: переставить части даты
echo '2024-01-05' | sed -E 's/([0-9]{4})-([0-9]{2})-([0-9]{2})/\3.\2.\1/'
# жадность: .* захватывает максимум
echo '<a><b>' | grep -oE '<.*>'
# ленивый вариант есть только в PCRE
echo '<a><b>' | grep -oP '<.*?>'
# переносимая замена ленивости — класс-отрицание
echo '<a><b>' | grep -oE '<[^>]*>'
# PCRE: просмотр назад и \K — совпадение без префикса
echo 'id=42' | grep -oP '(?<=id=)\d+'
echo 'id=42' | grep -oP 'id=\K\d+'
# PCRE: слово, после которого НЕ идёт цифра
echo 'v1 vx' | grep -oP 'v(?!\d)\w'
# спецсимвол как обычный: точка и звёздочка
printf '1.5\n105\n' | grep '^1\.5$'
printf 'a*b\naab\n' | grep 'a\*b'
Подводные камни
- Точка — любой символ.
grep '1.2'найдёт и1x2. Экранируй (1\.2), бери в скобки (1[.]2) или используйgrep -Fдля поиска строки как есть. - Классы POSIX — только внутри скобок.
[:digit:]без вторых скобок означает набор символов: d i g t; GNU grep в этом случае останавливается с ошибкойcharacter class syntax is [[:space:]], not [:space:]. \dвне-P— это букваd, а\tв grep — букваt. Пиши[0-9]или[[:digit:]], а табуляцию передавай как$'\t'.- Нет ленивых квантификаторов в BRE/ERE.
.*?вgrep -Eиsed -Eработает как обычный жадный.*. Используй[^…]*илиgrep -P. ^и$в BRE специальны только по краям.grep 'a^b'ищет буквальноеa^b.*в начале шаблона — обычный символ в BRE:grep '*a'ищет*a. В ERE GNU grep принимает его с предупреждением* at start of expression.- Регулярка ищет подстроку.
grep '[0-9]'найдёт строкуabc1. Чтобы проверить строку целиком, ставь^…$илиgrep -x. - Шаблон в кавычках. Без одинарных кавычек оболочка сама обработает
*,?,[…],$и\. - Диапазоны и
.зависят от локали. В песочнице локальPOSIX:.— один байт,[a-z]— ровно латинские строчные, а[[:alpha:]]не включает кириллицу. СLC_ALL=C.utf8.совпадает с целой буквой, и[[:alpha:]]находитё.