Скриптовые языки и обработка текстов
Лекция читается также для слушателей курса кафедры АЯ «Парадигмы программирования».
- Базис
- Алгоритм + структура данных = программа. - (примем это за рабочую гипотезу)
- Текстовое представление исходных кодов программ — для человека 
- Произвольное представление исходных данных — только для роботов? 
 
- Тезиc
- Данные тоже должны быть текстовыми 
- Антитезис
- Что же, всё теперь руками делать? - Основной элемент командной строки ОС —программа. Программа сама себя не запустит, и результатов своей работы не обработает!
- Предположим, исходные данные не текстовые. Мы их преобразовали в текст, посмотрели, а дальше что? 
 
- Синтез
- Пускай текст управляет текстом! - Языки склейки и алгоритмически полные командные интерпретаторы - Более точно: язык склейки (скриптовой) — язык программной манипуляции интерфейсами прикладной среды.
- В нашем случае — текстовыми - Конкретный случай: unix shell, в котором в качестве среды выступает множество утилит (программ) и файлы. 
 
 
- Всевозможные языки обработки текстов: - Языки контекстной обработки (например, основанные на регулярных выражениях)
- Макрообработка
 
 
- Языки склейки и алгоритмически полные командные интерпретаторы 
Выглядит как план целого С/К, правда?
Командная оболочка
Глава из учебника Unix shell как язык склейки:
- Среда: утилиты и файлы *nix-подобной системы
- Интерфейс — командная строка - В действительности, современный шелл (например, bash) — ЯП, интерпретатор командной строки и язык склейки одновременно
 
Особенности:
- Команды — программы, запуск по имени, $PATH
- Перенаправление ввода-вывода, конвейерная обработка
- Текстовые переменные, подстановка вывода
- Запуск процессов в фоне, обработка ошибок и условные операторы
- Минимализация синтаксиса для основных задач скриптования
- …
Кроме того:
- Диалекты shell
- Принцип совмещения интерпретатора, ЯП и оболочки
Контекстная обработка
Нужно много инструментов по обработке текстов!
- Анализаторы и преобразователи размеченных тестов в стандартных форматах (например, конфигурационных файлов)
- Анализаторы и преобразователи структурированных гладких текстов — вывода утилит, произвольных конфигурационных файлов, самих скриптов и т. п. 
- Принцип контекстной адресации
- Программа состоит из блоков, каждый из которых обрабатывает заданную часть текста - Чаще всего текст читается построчно - строки обычно достаточно
- строка — неплохой контекст для РВ
 
 
- Чаще всего текст читается построчно 
Регулярное выражение
(нужно не менее лекции, так что не сейчас)
- Мощный инструмент анализа/преобразования текста
- Требует строгого ограничения контекста (пример: поиск строковой константы а ЯП)
- имеет 100500 диалектов ☺
Макроподстановка
Задача: параметризовать текст.
- Подставить пути до каталогов
- Вставить шаблон (вместо короткого текста — длинный)
- Вставить параметрический шаблон 
- Вставить шаблон, параметры которого, как оказалось, тоже шаблоны ☺
- Вставить текст/шаблон в зависимости от условия
- Преобразовать строки
- Работать с файлами
- …
- Отказаться от построчной обработки 
Самый популярный вариант — препроцессор Си и другие макронадстройки над языками.
M4
Чуть ли не единственный из известных.
- Macro Magic: M4 Complete Guide 2019 года 
- учебник в составе autoconf (обратите внимание: в autoconf кавычки переопределены в [ и ]) 
Принцип:
- На входе — текст, содержащий команды M4
- Каждая команда, по мере поступления, выполняется, подставляя вместо себя результат своего выполнения (операция макроподстановки)
- Этот результат может, в свою очередь, содержать команды M4, поэтому анализ текста возобновляется с позиции, в которую была сделана подстановка
- На выходе — текст, не содержащий команд M4 
Вот этот текст разумный http://mbreen.com/m4.html
Примеры просьба прощёлкивать! Если нет linux под рукой — можно воспользоваться любым online linux-окружением, например http://repl.it. При открытии редактора там в правой части запускается натуральная linux-консоль.
В ней можно запустить m4, но лучше что-нибудь вроде cat > o; echo "===="; m4 < o, чтобы ввод и вывод не перемешивались. Ввод заканчивается новой строкой и Ctrl+D для обозначения конца файла.
Макроопределение и макроподстановка
define(AUTHOR, W. Shakespeare) `AUTHOR' is AUTHOR
- Однако
define(AUTHOR, Me) define(AUTHOR, A. Maclean) `AUTHOR' is AUTHOR or Me?
- А это не заработает:
define(AUTHOR, W. Shakespeare) define(AUTHOR, A. Maclean) `AUTHOR' is AUTHOR
- Так что лучше брать макрос в кавычки всегда, и обмазывать dnl от лишних переводов строки 
define(`AUTHOR', Me)dnl define(`AUTHOR', A. Maclean)dnl `AUTHOR' is AUTHOR or Me?
Вложенность кавычек
define(`definenum', `define(`num', `99')') num definenum num
- В то время как
define(`definenum', define(`num', `99')) num
Условные операторы
ifdef()
Только на сопоставление
define(`a', b)dnl ifelse(a, b, c, d) ifelse(a, B, c, d)
- и похитрее
define(`a', d)dnl define(`e', b)dnl ifelse(a, b, b-true, c, c-true, d, d-true, all-false) ifelse(e, b, b-true, c, c-true, d, d-true, all-false)
Типы данных
- eval() — вычисление арифметических выражений 
- translit(), index(), substr(), len(), regex(), … — строки 
translit(`Highgest leet of all', `etl', 371)
- (кавычки можно не ставить, но мало ли: слева точно литералы, а справа может быть и макро)
Параметрические макросы и циклы
Параметры — это просто $№ в теле макроподствновки
- $# — количество, $* — все сразу, $@ — все сразу, но закавыченные (без дальнейшей макроподстановки) 
define(`NONTERM', non-terminal)dnl define(`param', `All: $* All-quoted: $@ Number: $#; Second: $2')dnl param(one, two by two, NONTERM, three) param(`one', `two by two', `NONTERM', `three')
Цикл == рекурсия!
- define(`len',`ifelse($1,,0,`eval(1+len(substr($1,1)))')')dnl len(qw qw) len() len 
Остальные циклы (forloop, foreach cмоделированы в соотв. библиотеках)
Потоки вывода
Часть текста можно перенаправить в синтетический поток, по окончании работы M4 они припишутся в конец в порядке нумерации (0 — основной поток)
one divert(3)dnl two three divert(1)dnl four five divert(0)dnl six seven
Поток -1 не направляется никуда (а определённые макросы остаются!), а ещё поток можно закрыть и вставить по месту с помощью undivert()
divert(-1) Здесь можно писать что угодно значение имеют только макроопределения define(`c', > $1) divert(0)dnl c(выхожу один) divert(1)dnl c(я на дорогу) divert(0)dnl c(сквозь туман) undivert(1)dnl тернистый путь блестит
Далее везде…
- pushdef() и стеки определений (смена контекста) 
- Поддержка примитивов склейки (system()) 
- Набор библиотек
- …
- Макровзрыв
Фактически, декларативный алгоритмически полный язык
Потоковый редактор sed
- sed — руководство 
Общая структура программы список инструкций вида:
- <контекстный_адрес><команда> <контекстный_адрес><команда> … 
- Обработка текста идёт построчно
- Каждая строка, удовлетворяющая контекстному адресу, обрабатывается командой
Контекстный адрес:
- номер строки или $ 
- номер строки~шаг 
- /РВ/ (или \∀РВ∀, где ∀ — любой) 
- адрес,адрес — от, до 
Команды:
- d — удалить строку, a/i — добавить несколько, с — заменить 
- y/что/на что/ — преобразование символов по аналогии с tr 
- поиск с заменой в строке: s/РВ/подстановка/ 
- Условный оператор: t метка — если предыдущая замена была успешна - безусловный переход: b метка 
- собственно, метка: : метка 
 
- работа с буфером: g G h H x 
- { … } — группировка инструкций в одну команду 
- всякое
Примеры:
- cal | sed -E 's/[^[:digit:][:space:]_]/@/g' 
- ldd /lib64/lib*.so.* | sed -n '/^\//h;/libcrypto.*=/{x;p;x;p}' 
- locate .sed — любой)
Алсо, полнота по Тюрингу повсюду
AWK
- История (фамилии какие!) - Там же неплохие короткие примерчики
 
- Старые учебники на русском: очень старый, тоже очень старый 
- GAWK: Документация, перевод 
Общая структура программы список инструкций вида:
- <контекстный_адрес> { <список команд> } <контекстный_адрес> { <список команд> }
- Обработка текста идёт построчно
- Каждая строка, удовлетворяющая контекстному адресу, обрабатывается командами из списка
Команды реализуют Си-подобный синтаксис
- Все переменные текстовые, численное преобразование автоматическое
- Ассоциативные массивы
- Строка автоматически разбивается на слова: - Доступны в переменных $1, $2, …; $0 — вся строка 
- Можно переопределять разделители полей (и строк!)
 
- Операция склейки строк: «"str1" "str2"» = «"str1str2"» 
- …
Контекстный адрес:
- выражение — строка удовлетворяет такому адресу, если выражение не пусто и не 0 - можно использовать конструкцию выражение~/РВ/ (и !~) 
 
- /РВ/ (фактически $0~/РВ/ 
- BEGIN, END (до начала, после конца файла) 
- пустой (для всех строк)
- Двухадресный — диапазон
Примеры:
- ldd /lib64/lib*.so.* | awk '/^\//{F=$1};/libcrypto.*=/{print(F, $3)}' 
- Наиболее востребованная особенность — полноценный поиск с заменой (gensub()) 
- Поддержка склейки (например, прозрачное перенаправление В/В, 
- Много функций (на уровне ЯП общего назначения) 
- Библиотеки
- …
Примеры
- Про склейку: cal | awk '/1/{print | "sort" }; /9/{print | "sort" }' 
- locate .awk ☺ 
Фактически, полноценный ЯП, на котором бы много писали, если бы не …
Perl
Не пойдём дальше Википедии
- ЯП общего назначения
- Имеет неплохую объектную модель и структуру типов
- Поддерживает контекстную адресацию
- Поддерживает примитивы склейки
- CPAN огромная библиотека модулей 
- Минималистичный и очень гибкий синтаксис
- …
- …
- Стремительно теряет популярность ☹
???
Особенности Linux
- bash неизбежен
- gawk, а не awk (есть ещё mawk/nawk, но в целом нет)
- GNU sed, GNU m4
- perl 99.9% присутствует
- масса других ЯП, в разной степени скриптовых — может, лучше они?
Autoconf и m4
- M4 используется в autoconf. Собственно, autoconf написан на m4. В действительности configure.ac — это шелл-скрипт! Просто в нем одни только m4-макросы. А autoconf превращает его в configure. - Проверьте это! Добавьте в конец configure.ac shell-команду echo Done., найдите её в сгенерированном configure и в его выдаче при запуске. 
- Обратите внимание на то, что кавычки m4 переопределены в [ и ] 
 
Д/З
- Попробовать немного примеров по m4, sed и gawk (по ссылкам в плане есть изрядно примеров). Прочитать последнее замечание в плане этой лекции ☺ 
- Взять любой проект из домашних заданий, содержащий более одного файла на Си (например, .c и .h) и использующий autotools для сборки. - Пользуясь тем, что configure.ac — shell-скрипт с m4-макросами, добавить туда код на шелле, который для всех .h и .c файлов будет добавлять в начало каждого строку вида - /* COMMENT:: Полное название программы, версия: дата */ если её там нет, и обновлять её (заменять на новую), если она там есть,- …а также будет выводить эту строку на стандартный вывод.
- Всё это, разумеется, происходит при запуске ./configure 
 
- (Подсказка. Для начала можно отладить внешний сценарий, который заменяет/подставляет в начало какую-нибудь строку вида /* COMMENT::…*/) 
- Можно пользоваться sed -i или awk -i inplace (пример в конце описания), grep, если не получится, то вообще чем угодно из стандартного окружения. 
- Строка Полное название программы, версия: дата генрируется из соответствующих макросов AC_INIT, поэтому этот сценарий надо оформить как include-файл для configure.ac или просто вписать этот код в конец configure.ac 
- Убедиться, что полученная конструкция работает и не ломает сборку
 
- Поместить очищенный от генератов проект (по идее в нем будет изменён configure.ac и, возможно, добавлен include-файл для него) в подкаталог 12_ScriptingText целевого репозитория 
- Как примерно должно выглядеть решение: - Файл insver.sh: отладочный вариант, который подставляет/заменяет в .h и .c файлах строку /* COMMENT::…*/ (пока только с текущей датой, с помощью `date`): - 1 $ grep 'MSG=' insver.sh 2 MSG="COMMENT:: `date`" 3 $ head -2 *.c *.h 4 ==> cats.c <== 5 #include <stdio.h> 6 #include <glib.h> 7 8 ==> kitty.c <== 9 #include "cats.h" 10 #include "config.h" 11 12 ==> cats.h <== 13 #ifndef _MYMODULE_CAT_H_ 14 #define _MYMODULE_CAT_H_ 15 16 $ sh insver.sh 17 COMMENT:: Сб дек 5 12:28:12 UTC 2020 18 $ head -2 *.c *.h 19 ==> cats.c <== 20 /* COMMENT:: Сб дек 5 12:28:12 UTC 2020 */ 21 #include <stdio.h> 22 23 ==> kitty.c <== 24 /* COMMENT:: Сб дек 5 12:28:12 UTC 2020 */ 25 #include "cats.h" 26 27 ==> cats.h <== 28 /* COMMENT:: Сб дек 5 12:28:12 UTC 2020 */ 29 #ifndef _MYMODULE_CAT_H_ 30 31 $ sh insver.sh 32 COMMENT:: Сб дек 5 12:28:54 UTC 2020 33 34 $ head -2 *.c *.h 35 ==> cats.c <== 36 /* COMMENT:: Сб дек 5 12:28:54 UTC 2020 */ 37 #include <stdio.h> 38 … 39 
 
- Добавим туда макросы, которые определяются в configure.ac. Теперь если запустить insver.sh, в комментарии попадут нераскрытые макросы. 
- У нас два пути: - Объявить файл шаблоном в AC_CONFIG_FILES(), переименовать в insver.sh.in и добавить в строку подстановки макросы для имени и версии. В configure.ac вписать запуск генерата 
- Добавить в строку подстановки переменные shell, в которые эти макросы раскрываются, и за-include-ить его прямо в конец configure.ac - В autoconf открывающая и закрывающая кавычки переименованы в «[» и «]» (не используйте их в сценарии), а директива M4 include() — в m4_include() 
 
 
- Получится примерно следующее: 1 $ grep AC_INIT configure.ac 2 AC_INIT([GLib/GObject examples], [0.0], [george@altlinux.org]) 3 $ autoreconf -fisv 4 … 5 $ ./configure 6 … 7 COMMENT:: GLib/GObject examples, 0.0: Sat Dec 5 12:58:44 UTC 2020 8 $ head -2 cats.c 9 /* COMMENT:: GLib/GObject examples, 0.0: Sat Dec 5 12:58:44 UTC 2020 */ 10 #include <stdio.h> 
 
