Регулярные выражения

Библиотеки для работы с регулярными выражениями используются при 
программировании на Си. Непосредственно реализованы в ЯП Perl, Python, 
JavaScript, Tcl/Tk, Awk (Aho+Weinberger+Kernighan), а также в утилитах 
обработки текста sed (Stream Editor) и grep (Great Regular Expression) и 
в некоторых других программных средствах. 

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

Различают базовые регулярные выражения и расширенные. Расширения в
разных средствах различаются по объему.

Фундаментальной строительной единицей регулярных выражений является
регулярное выражение, соответствующее одиночному символу. Буквы, цифры и
большинство прочих символов соответствуют сами себе. Если же символ имеет
специальное значение, то предварение его обратной косой чертой отменит это
значение. Множество символов, заключенных в квадратные скобки, 
соответствует любому символу из этого множества, как и в bash. Если перед
множеством поставить знак ^, то это выражение будет соответствовать одному 
любому символу не из этого множества. Любой одиночный символ в отличие от
оболочки обозначается знаком точка. Если обозначить за r регулярное
выражение, то синтаксис расширенных регулярных выражений можно представить 
следующей таблицей. 

	  выражение  значение              
	  r1r2       r1 затем r2
	  r1|r2      либо r1, либо r2
	  r*         0 или более раз r
	  r+         1 или более раз r
	  r?         0 или 1 раз r
	  r{n}       n раз r
	  r{n,}      n или более раз r
	  r{,m}      m или менее раз r
	  r{n,m}     от n до m раз r
	  ^          начало строки
	  $          конец строки
	  (r)        группировка и пометка для ссылки
          \d         ссылка назад на d-ое помеченное выражение

В базовых регулярных выражениях символы |+?{}() нужно предварять знаком \.

Ссылки, в общем случае, могут требовать экспоненциального времени
обработки. Выражения с фигурными скобками могут потребовать много памяти.

(П) [A-Z][a-z]*  --- слова из английских букв, начинающиеся с заглавной буквы
    [A-Za-z_][A-Za-z_0-9] --- идентификатор си, паскаля
    [0-9]*\.[0-9]+  --- числа с плавающей десятичной точкой
    [^abc]*a[^abc]*b[^abc]*c[^abc]* --- строки, содержащие в 
       последовательном порядке буквы a, b, c 
    .*(zz|s{2})+.* --- строки, содержащие двойную z или s
    (.)\1 --- сдвоенный символ

Обычно регулярному выражению сопоставляется самая длинная подходящяя строка. 
Это называется жадной квантификацией. В некоторых расширениях можно 
использовать знак ? для указания на ленивость (нежадность) квантификации. 
Например, s* в строке sssss будут соответствовать все символы, а s+? 
только первая s.


      Программа grep

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

(П) Для поиска в файле fortest строк, содержащих слово computer, нужно 
ввести команду grep computer fortest. 

В общем случае формат вызова --- 
grep [ОПЦИИ] РЕГУЛЯРНОЕ ВЫРАЖЕНИЕ [ФАЙЛЫ ДЛЯ ПОИСКА]. Среди множества
опций особенно важны: 

  -E переключает grep на работу с расширенными регулярными выражениями (по умолчанию 
     используются базовые); 

  -f ФАЙЛ взять шаблон из файла;

  -i игнорировать различие между строчными и прописными буквами;

  -v инвертирует условие поиска, например, grep -v computer fortest означает 
     найти все строки файла fortest, не содержащие слова computer.

  -r искать не только в заданных каталогах, но и в их подкаталогах

  -n показывать номера строк

  -o выводить только ту часть строки, что соответствует заданному шаблону

Файлы для поиска не указываются, если grep используется как потоковый фильтр. 

(П) grep computer <fortest
    cat fortest | grep computer #аналоги предыдущего примера

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

(П) Текстовый файл words содержит словарь английских слов (в каждой
строчке одно слово). Нужно найти все слова, содержащие пару сдвоенных букв.

  grep -E '(.)\1.*(.)\2' /usr/share/dict/words

или

  grep '\(.\)\1.*\(.\)\2' /usr/share/dict/words.

(П) Найти все слова-палиндромы.

  grep -E '^(.).?\1$|^(.)(.).?\3\2$|^(.)(.)(.).?\6\5\4$' /usr/share/dict/words; \
    grep -E '^(.)(.)(.)(.).?\4\3\2\1$' /usr/share/dict/words  #до 9-и букв длиной

(П) Найти все слова из 6 и более букв, в которых буквы следуют в
алфавитном порядке.

  echo '^a?b?c?d?e?f?g?h?i?j?k?l?m?n?o?p?q?r?s?t?u?v?w?x?y?z?$' >pattern
  grep -Ef pattern /usr/share/dict/words | grep '......'

(П) Найти все файлы из каталога /usr/include, содержащие слово SIGINT.

  grep -r SIGINT /usr/include/*


      Программа аук (awk)

В отличие от программы grep, являющейся пассивным фильтром, awk позволяет 
проводить активную фильтрацию, т.е. не только отбирать нужные данные, 
но еще и модифицировать отобранные данные. Описание активного фильтра 
awk --- это программа на языке awk. Вызов awk подобен вызову grep 
--- awk [ОПЦИИ] [ПРОГРАММА] [ФАЙЛЫ ДЛЯ ОБРАБОТКИ]. Файлы для обработки 
не указываются, если awk используется как фильтр. Наиболее используемая
опция --- -f, после которой указывают имя файла с программой. Также бывает
полезна опция -b, которая устанавливает побайтовый (непосимвольный) режим
ввода-вывода. Программы на awk могут иметь длину в сотни строк. Общий вид
простой awk-программы:

  ШАБЛОН {ДЕЙСТВИЕ}
  ...

Программа читает по одной строке из входного потока или указанных файлов.
Каждой из этих строк сопоставляются по-порядку шаблоны. При соответствии 
строки шаблону выполняется действие, связанное с шаблоном, и затем переход к
следующему шаблону. Таким образом, если строка соответствует нескольким
шаблонам, то для нее выполняются действия, связанные со всеми этими
шаблонами. Прервать переход к следующему шаблону можно командой next --- она 
означает считать новую строку и перейти к первому шаблону.

Вызов awk '/РЕГУЛЯРНОЕ ВЫРАЖЕНИЕ/' [ФАЙЛЫ] почти
эквивалентен grep -E 'РЕГУЛЯРНОЕ ВЫРАЖЕНИЕ' [ФАЙЛЫ]. Шаблоны строятся
в основном из расширенных регулярных выражений с почти таким же 
синтаксисом, что и в grep. Различие только в том, что в awk нельзя 
использовать ссылки. Список всех допустимых шаблонов awk:

BEGIN --- соответствует началу работы, до прочтения первой строки;

END --- соответствует концу работы, после обработки последней строки;

/РЕГУЛЯРНОЕ ВЫРАЖЕНИЕ/ --- соответствует строке, содержащей в себе
подстроку, соответствующую регулярному выражению

ЛОГИЧЕСКОЕ ВЫРАЖЕНИЕ --- соответствует строке, если истинно

ШАБЛОН && ШАБЛОН --- соответствует строке, если оба шаблона ей
соответствуют;

ШАБЛОН || ШАБЛОН --- соответствует строке, если хотя бы один из
шаблонов ей соответствует;

ШАБЛОН ? ШАБЛОН : ШАБЛОН --- если 1-ый шаблон соответствует строке, то 
осуществляется проверка соответствия строки 2-му шаблону, иначе --- 3-му;

! ШАБЛОН --- соответствует строке, если шаблон строке не соответствует;

(ШАБЛОН) --- группировка;

ШАБЛОН, ШАБЛОН --- диапазонный шаблон, соответствует всем строкам, 
начиная от строки, соответствующей 1-му шаблону, до строки, соответствующей 
2-му.

Первые два и последний шаблоны можно использовать только самостоятельно.

Помимо стандартных отношений (<, >, <=, >=, ==, !=) в логических выражениях 
можно использовать операции ~ (соответствует), !~ (не соответствует) и in
(является членом).

(П) "abc" ~ /^a.*c$/ --- верно
    "a123de4517f" ~ /[1-7]*/ --- верно
    "abcd" ~ /[0-9]+/ --- ложь
    "abcd" !~ /^b.*/ --- верно

Синтаксис действия является си-подобным. В отличие от си концом оператора
является не только точка с запятой, но и переход на новую строку. 
Операторы if, while, do, for, continue, break, return, составной --- аналогичны 
операторам си. Кроме того, в awk есть еще операторы for in, delete и exit. 
Последний аналогичен команде exit оболочки bash. Операции в awk те же, что и 
в си, но знак ^ означает возведение в степень и отсутствуют поразрядные 
операции и операция следования. Кроме того, знак $ используется подобным 
оболочке образом и для склейки строк достаточно записать их подряд или через 
пропуск. Комментарии такие же как в bash. Пустой шаблон соответствует 
любой строке. Действие по-умолчанию --- печать входной строки.
Входная строка именуется $0. Входная строка рассматривается как запись, 
которая разбивается на поля символами из множества, 
определяемого переменной-строкой FS (инициализируется 
пробелом). К каждому полю можно обращаться по его номеру: 1-ое именуется 
$1, 2-ое --- $2 и т.д. По каждой строке устанавливаются переменные NF --- 
число полей в ней и NR --- ее номер. Любое значение awk сначала пытается 
обрабатывать как число. Если это невозможно, то значение рассматривается 
как строка. Переменные не описываются и инициализируются 0.

(П) awk '{print}' /etc/passwd  #распечатка файла
    awk '{print ++n, $0}' /etc/passwd  #распечатка с номерами строк
    awk '{print NR, $0}' /etc/passwd  #то же самое
    echo 1 2 3 5 | awk '{for (i = 1; i <= NF; i++) sum += $i
      print sum}'  #печать суммы чисел из входного потока

Функция print без параметров печатает входную строку. Для форматной печати
можно использовать функцию printf, практически такую же как в си, но
аргументы которой можно задавать и без скобок.

(П) ls -l  #печать подробной информации о файлах текущего каталога
    ls -l | awk '{
      size += $5; count++}
      END {printf "Всего файлов %14d\n", count
        printf "Общий объем данных %8d\n", size
        printf "Байт на файл %14.2f\n", size/count}'

Можно использовать следующие математические функции:

atan2(x,y) --- арктангенс от x/y;

cos(x), sin(x) --- косинус и синус от x;

exp(x), log(x) --- экспоненциальная функция и натуральный логарифм от x;

sqrt(x) --- квадратный корень от x;

int(x) --- целая часть числа x;

rand() --- случайное число из диапазона от 0 до 1;

srand() --- инициализация генератора случайных чисел.

(П) awk 'BEGIN {for (i = 1; i <= 10; i++) printf "%2d %.2f\n", i, sin(i)}'
       #печать значений синусов чисел от 1 до 10

Для работы со строками можно использовать функции:

gsub(r, s, t) --- замена каждой подстроки в строке t, соответствующей 
регулярному выражению r, на строку s. Результат вызова --- количество 
произведенных замен. Знак & в s заменяется на нашедшую соответствие 
подстроку. Для использования знака & в s его надо предварять обратной косой 
чертой. Если t не указано, то берётся $0;

(П) t="aya"; gsub("a","b",t); print t #byb

sub(r, s, t) --- то же, что и gsub, но делает не более одной замены;

index(s, t) --- позиция строки t в строке s или 0, если t в s не входит;

length(s) --- длина строки s;

match(s, r) --- позиция подстроки, соответствующей регулярному выражению r,
в строке s или 0, если такого соответствия нет. Устанавливает переменную
RLENGTH равной длине найденной подстроки;

split(s, ar, r) --- создание и заполнение ассоциативного массива ar полями 
строки s, разделитель полей задается регулярным выражением r или FS, если r 
не указано. Возвращает число полей, элементов массива ar;

substr(s, i, n) --- подстрока s с позиции i либо длиной n, либо если n не
указано, то до конца строки; 

sprintf(fmt, e1, ..., en) --- строка-результат форматной печати выражений от 
e1 до en согласно строке формата fmt. Во всем кроме результата идентична 
printf;

strtonum(s) --- переводит строку s в число, в gawk десятичные числа
переводятся автоматически, а 8-е и 16-е только c опцией -non-decimal-data,
другие варианты awk все числа переводят автоматически и такой функции не имеют;

tolower(s), toupper(s) --- строка s, в которой все буквы заменены на строчные
или заглавные соответственно.

Строки в awk не являются массивами в отличие от Си.

(П) Печать первых 30 символов строк длины, большей 50 символов.
Сопровождать вывод таких строк их номерами.

  length($0)>50 {print NR, substr($0, 1, 30)}

Массивы awk --- ассоциативные, в них качестве индексов могут
использоваться строки. Для проверки вхождения индекса в массив используется
операция in. Для удаления всего массива или его компоненты используется
операция delete.

(П) Рассмотрим фрагмент программы.

  w["cat"] = 2
  w["dog"] = 3
  w["cow"] = 10
  w["mouse"] = 1
  for (i in w) print i, w[i]  #распечатка массива w
  w["goat"] = 5
  delete w["cow"]
  print "-----"
  for (i in w) print i, w[i]
  if ("goat" in w) print "ok."
  s = "cat dog wolf tiger shark"
  n = split(s, w,  " ")
  for (i in w) print i, w[i]  #неупорядоченно
  for (i = 1; i <= n; i++) print i, w[i]  #по порядку
  delete w  #уничтожение всего массива w
  (П) Программа составления частотного словаря.
  BEGIN {FS = "[^A-Za-z]"}  #установка разделителей полей
  {
  for (i = 1; i <= NF; i++) 
    dict[toupper($i)]++
  }
  END {
    delete dict[""]
    for (i in dict) 
      print i, dict[i]
  }

Если назвать программу fd.awk, то запустить ее можно так

  awk -f fd.awk <файлы для обработки>.

Чтобы словарь получился отсортированным можно добавить | sort.

Если нужно, чтобы результаты печатались не в стандартный поток, а в заданый 
файл, то к команде печати добавляют перенаправления > или >>, например,
  if (a<10)
    print "Variant 1" > "file1"
  else
    printf "Variant 2 (a=%d)\n", a > "file2"
выводит печатаемый текст либо в file1, либо в file2. При использовании > 
создается новый файл, а при использовании >> --- данные добавляются к 
существующему файлу. Можно еще организовывать при помощи | трубопровод.

Для ввода можно использовать функцию getline, вызов которой без параметров
читает строку в $0, устанавливает NF и т.п. Если вызвать getline с
параметром-переменной, то строка читается в неё. Для этой функции естественно
использовать перенаправление <.

Если нужно рассматривать символы как байты (а не байтовые последовательности
как, например, в UTF-8), то используется опция -b.

Кроме конструкции шаблон---действие в awk можно определять новые функции
конструкцией

function ИМЯ ФУНКЦИИ (СПИСОК ФОРМАЛЬНЫХ ПАРАМЕТРОВ) {ОПЕРАТОРЫ}.

Массивы передаются по ссылке, прочие параметры --- по значению. Можно
указывать фактических параметров меньше, чем формальных, тогда
неиспользованные формальные можно использовать как локальные переменные,
инициализированные 0 или пустой строкой.

(П) function sqr (x) {return x*x}
    BEGIN {printf "%d*%d=%d\n", 5, 5, sqr(5)} #5*5=25


      Поточный редактор sed
Может рассматриваться как упрощенный awk. Основные команды: замена (s) и 
удаление (d). Например, 
   sed 's/abc/cba/g' ФАЙЛ
заменит все вхождения строки abc на cba в файле и выведет результат в 
поток вывода. Замена специфицируется командой s (seek), у которой два 
аргумента, 1-й --- регулярное выражение, описывающее то, что надо заменить, и 
2-й --- строка-замена. В замещаемом можно использовать цитированные бэкслэшем 
скобки для выделения подвыражений, а в замене --- & для указания на все 
замещаемое или ссылки от \1 до \9 на выделенные подвыражения, например, 
s/a+(b+)c/&-\1/ заменит aabbcd на aabbc-bbd. После аргументов может идти 
опция g, означающая повторность замен в строке, --- без нее будет 
производиться не более одной замены на строку. 

Пример. Вызов 

sed 's/<[^>]*>//g' file.html > file.txt

уберет все теги HTML, кроме многострочных, из исходного файла, преобразуя его 
в немаркированный текст.

Вместо / в s можно использовать любой символ, например, s:/://: заменит / на 
два.

Команды sed, как и awk, могут иметь предваряюший шаблон. Например, /r/s/b/d/ 
будет заменять первую b на d только в строках, содержащих r, а команда 
/r/!s/b/d/ будет осуществлять ту же замену, но в строках, r не содержащих.

Команда d (delete) используется с шаблоном, например, /[0-9]/d удаляет все 
строки с десятичными цифрами, а 1~2d удаляет все нечетные строки. Запись 1~2 
означает с 1 строки с шагом 2, шаг можно опускать. В sed можно использовать и 
другие шаблоны, например, подобные диапазонным awk.

Команды разделяются точкой-с-запятой или концом строки. Всего в sed более
десятка команд, среди которых переходы, условные переходы, обмен с единственной
областью для хранения данных, перекодировка, работа с файлами и т.п.

Опция -r позволяет использовать расширенные регулярные выражения, а опция -f 
такая же как в awk или grep.

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

Пример.  Удаление всех тегов HTML, включая многострочные, из документа без
комментариев.  Создадим файл nohtml.sed со следующей программой.

1 h
2~1 H
$ {
   g
   s/<[^>]*>//g
   p
}

Вызов

sed -nf nohtml.sed file.html >file1.txt

или

sed -n '1h;2~1H;${g;s/<[^>]*>//g;p}' file.html >file2.txt

Опция -n подавляет автоматическую печать каждой строки.  В построчном режиме
каждая строка печатается автоматически после обработки.  Команда p (print, 
печать) используется обычно вместе с опцией -n, она печатает текущие данные.

Шаблон 1 - это первая строка.  Шаблон 2~1 - это все строки, начиная  со второй.
Шаблон $ - это последняя строка.  Команда h переносит текущие данные, входную
строку, в буфер, а команда H добавляет к буферу маркер конца строки и затем
текущие данные.  Первые две команды sed-программы обеспечивают перенос всего
входного текста в буфер.  Команда g, обратная к h, переносит содержимое буфера
в рабочую область, делает содержимое буфера текущими данными.  Фигурные 
скобки используются для группировки.  Знак ; используется как разделитель
команд, конец строки ему эквивалентен (как в awk).  Комментарии как в bash.