Скриптовые языки и обработка текстов
Лекция читается также для слушателей курса кафедры АЯ «Парадигмы программирования».
- Базис
Алгоритм + структура данных = программа.
- (примем это за рабочую гипотезу)
Текстовое представление исходных кодов программ — для человека
Произвольное представление исходных данных — только для роботов?
- Тези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>