Регулярные выражения
В линуксе две максимы - все есть файл, файл есть текстовый, рассмотрены ранее.
Добавляем третье пожелание - human-readable. файл хотя бы кусками должен помещаться в голову администратора.
/etc - конфиги, текстовые, все можно открыть и прочитать. Синтаксис разный.
по сути почти все конфиги - куски программ.
еще одна максима, от нее постепенно отходят - файлы human-writeable - при желании можно изменить все, что угодно. не соответствуют, например, многометровые нагенерированные конфиги.
Инструментов, работающих с human-writeable конфигами много. те же coreutils применяются.
имеем конфигурационный файл, например, из инита. с синтаксисом доставшимся в один момент от виндовса - с квадратными скобками и всякими прочими завихрениями. есть модули для автоматизированного изменения конфигов
ls ~/.??*
Зачем начинать название конфига с точки? Потому что по умолчанию эти конфиги не показываются командами типа ls.
Нужен ли нам для модификации структурированного файла парсер целиком? Почти никогда не нужен. Чтобы поменять значение одной переменной не нужно всасывать всю синтаксическую структуру.
Нам нужно только определить контексту, определить строку и место где менять.
Зачем разные секции? Так удобней, могут быть в разных секциях одинаковые параметры. Но конечный вариант конфига определяется писателем софта -- может быть хмлем, может быть волщебным со скобочками, много каким может быть.
Итак, с процедурой модификации связано
- определение контекста(как правило это строка)
- мелкая правка (замена чего-то на чего-то, выбрасывание всего кроме пейлоада (чтобы посмотреть)).
Что это означает?
Те инструменты, которые занимаются редактированием такх текстов должны включать эти три возможности.
Разберемся сначала с поиском. Достаточно ли шаблонов для определения контекста? Нет, недостаточно, потому что изначально шаблоны использовались для генерации имен файлов, то есть очень простом синтаксическом поле. Язык шаблонов отлично подходит для имен файлов, но не более. Более эффективный инструмент называется регулярные выражения, это инструмент сопоставления габона и его подстроки. То есть, фактически, определение контекста. Вы описываете контекст и можете привязывать команды к этому контексту.
Теоретическую теорию, начинавшуюся с Хомского мы опустим. Он разработал классификацию формальных языков. Регулярные выражения это выражения, описываюшие регулярные грамматики -- самые жесткие из класс. Хомского, то есть это наименьший класс языков.
Тем, кому интересно можно почитать MRE Mastering Regular Expression это не домашнее задание, но она толстая и ужасно интересная. 1 издание заканчивалось 16 страничным рег выражением.
Там много подробностей о том, как устроено внутри.
Что такое рв? Это шаблон, задача которого замачится с подстрокой.
Любой символ сопоставляется такому же символу.
Это диапазон в квадратных скобках [a-zA-Z0-9_] соотв одному символу из этого диапазона. Дополнение диапазона ^[].
Два сопоставления позиции - начало и конец строки. ^a -- префикс, a$ -- суффикс.
Символ . сопоставляется любому символу.
В рв есть специальные (. * ^ $) и неспециальные символы.
К регулярному выражению можно прикрепить повторитель. Самый страшный повторитель * -- соотв повторению от 0 до бесконечности раз. .* соответствует вообще любой строке, включая пустую.
Есть ешё ? -- это сопоставление 0 или 1 раз.
{} -- тоже повторитель.
Специальный символ, лищающий символ его управляющести или предающий её ему \.
k\{7\} -- k повторенное 7 раз.
Для удобства и запутывания всех людей на свете были придуманы расширенные регулярные вывражения, не расширяющие класс языков.
{} спец символ
+ - повторитель один и более раз
| Оператор или
Как теперь решать нашу задачу поиска в контексте?
начало ^\[нужная секция\]
конец ^\[
строка .*=
Все три операции -- опр. начало и конца контекста и поиска строки внутри него.
Обозначение контекста это одна операция, поиск внутри -- вторая, необязательно их объединять в одну атомарную.
В грепе, например, нет определения контекста, только поиск.
Если вы хотите чтобы греп выдал все строки с равно, но не тогда когда оно встречается в названии секции
^[^[].* =
А если есть строка вида a = b = c ? нужно ли что-то менять в предыдушем рв? Не нужно.
Какой подсторке сопоставится оно? Всему до c. Здесь мы упираемся в забавный факт. Чтобы пользоваться рв необязательно понимать, как они работают (возражения из зала).
Повторителе жадные. Самый левый самый длинный.
.*.* в abcde
первая половина заматчит всю строку, а вторая ничего.
a.*a.* в abacadae первая половина abacad вторая ae
Обратите внимание на то, каким образом движок дошел до жизни такой. Сначала он первую половину заматчил во всё, а потом откатывался пока не нашёл.
Сколько пустых строк содержится в abcde -- 7. Чертей на кончике иглы умещается сколько угодно. Нормальный алгоритм DFA, но поиска с заменой там не будет.
Поставим вместо * + -- это гораздо менее страшный повторитель (a.*b)+ Это выражение можно упростить в a.*b
Ещё один намек, почему при использовании повтрителей надо думать.
При поиске с заменой всё хитрее, но каждому овощу свой фрукт.
Как именно разложился матчинг в выражении, если вы просто ищете -- неважно.
Если вы ищете с заменой, то уже не так.
Начнем с редактора сед, а уж потом про поиск с заменой.
Греп активно использует рв, но предназначен только для фильтрации. Интересна же ситуация потокового редактирования, когда вы подсовываете файл и программу вида "контекст-операция".
Самым первым таким был редактор ed. Он выдает командную строку, в которой вы можете писать команды вида -- напечатать, удалить, итд.
g/re/p -- команда ед, которая делает тоже, что греп.
Редактором ед заниматься не будем, хотя например утилита дифф может выдать различия в формате, который ед потом может применить к файлу.
Рассмотрим редактор sed -- stream editor. Ему дается маленькая программка и входной файл. Он полноценный редактор, с его помощью с файлом можно сделать что угодно.
Вставка строчки команда i , a
Удаление строки d
h - запомнить строку, g - вспомнить (есть карманчик для запоминания)
Эти командны можно сопроводить контекстным адресом -- это либо номер строки, либо диапазон, либо регвыр. Если регвыр, то команда будет пприменена ко всем строкам, которые матчатся с ним. В гну сед есть вариант -- от регвыра до регвыра.
Если же вы хотите изменить строку то есть две команды -- замена символа и команда s/re/найти/флаги -- замена.
Есть ешё команды перехода и условного перехода (удалась подстановка -- перейти, не удалась -- не перейти).
В чем смысл команды с заменой?
Вернёмся к секционированному файлу. /*\[нс\]/,/*\[/s/^A=B$/A=C/ Предположим было написно
A = что-то1 - что-то2, а мы хотим заменить на А = что-то2 - что-то1
Тогда пишем
s/^A=(.+) - (.+)/A = \2 - \1/
Мы воспользовались "карманом" -- круглыми скобочками, они зовутся back references
Диалектов регвыров около 20 -- в питоне, в перле, в джаве, везде свои. Называются flavours. Разночтения обычно касаются character classes , обозначения начала и конца, многие мелкие удобные веши.
Возвращаемся к общей ситуации редактирования. Далеко не всегда редактировать тексты удобно программой на седе, хотя он и алгоритмически полон. Хотелось бы редактировать в редакторе, но иногда вызывать вот такие вот команды. У вима специфический подход -- редактирование текста это редактирование, а не просто набивание текста само по себе. Все что он умеет не знает даже автор, потому то туда каждый день приходит по патчу. От сочуствующих.
Есть билблиотека pcre, которая реализует иррегулярные выражения из перла. Главное отличие -- в них есть нежадные повторители и пред и пост просмотр. look behind и look ahead, помимо того, что ими можно зациклить, полезны ещё возможностью задать негативные контексты -- заматчить слово если перед ним не стоит какое то слово. Ради него их обычно и используют.