Обработка текстов и сценарии
Более плотная лекция 2020 года на похожую тему
Задача склейки
Командная строка как основной интерфейс управления
⇒ Понятие сценария:
- Использование тех же самых команд
- Алгоритмическая полнота (по возможности)
- Удобная суперпозиция команд (подстановка)
- «Админское программирование»: экономия объёма получающегося текста программы
Почему это часто встречается при сборке? Рецепты в make — это именно сценарии shell!
- Простые команды, которые проще написать на шелле, чем городить отдельный синтаксис make 
- Нестандартные сценарии и рецепты
- Костыли
Shell
- Интерпретатор командной строки - редактор командной строки, история, достраивание, alias-ы, …, управление заданиями и многое другое
 
- ЯП ВУ (функции, условные операторы и циклы, в некоторых шеллах — массивы)
- Ориентация на работу с текстом / файлами / процессами — склейку 
Глава из учебника про shell
(Интерпретатор: редактор командной строки, история, достраивание, alias-ы, …, управление заданиями)
ЯП с упором на склейку
- Переменные и их подстановка с простейшей пост-обработкой
- Встроенные команды и программы-команды - ⇒ «Сколько команд в командной строке»?
- ⇒ управление процессами (& и wait) 
 
- Перенаправление ввода-вывода - Вывод в строковое выражение `команда`, оно же $(команда) 
- >, < и 2> 
- >> и << 
- |, великий и могучий! 
 
- Условный оператор и циклы - Exit status как условие
- ⇒ Утилита test (она же — утилита [) 
- ⇒ Внутренние команды sh с exit status-ом 
 
- Генерация имён файлов   
- case и шаблоны   
- Базовая арифметика $((expr))   
- Обработка сигналов (см. пример ниже)
- …
Утилиты для работы с файловой системой и процессами
Основная документация: GNU coreutils
- всякая классика типа cp/mv/ls и т. п.
- Пример: как гарантировано удалять временные файлы: - 1 #!/bin/sh 2 exit_handler() { 3 trap - EXIT 4 test -r "$TMPFILE" && rm -vf "$TMPFILE" 5 } 6 7 trap exit_handler EXIT HUP INT QUIT PIPE TERM 8 9 echo -n "Create a file? " 10 read YN 11 if [ "$YN" = y ]; then 12 TMPFILE="`mktemp`" 13 echo "$TMPFILE" 14 ls -l "$TMPFILE" 15 fi 16 echo -n "Wait…" 17 read 18 echo "Done" 
 
- …
Утилиты работы с текстом
Проблема локали:
Основная документация: GNU coreutils
- cut, tr, tail, head, sort, paste, join, их много… (coreutils) - Пример: join 
 
- hexdump из Util-linux, xxd из vim и od из coreutils 
- Just for fun: moreutils 
- …
Комбайны
- grep для поиска 
- sed для поиска с заменой: - $ cal | sed 's/\([01]\)\([2-3]\)/\2:\1/g' Октябрь 22:01 Пн Вт Ср Чт Пт Сб Вс 1 2 3 4 5 6 7 8 9 10 11 2:1 3:1 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
 
- awk (или perl) для написания программ, управляемых контекстом - можно и sed, но… 
- а можно и любой ЯП. но будет длинно 
 
- m4… 
- …
Что может понадобиться Для решения Д/З
- Перенаправление ввода-вывода
- tput (cup и clear, было на лекции про терминал) из пакета termutils 
- Арифметика в shell (конструкция вида $((…))) 
- Параметры командной строки shell-сценария (конструкция вида $1, $2 и т. п.) 
- Цикл while в shell 
- shuf — собственно рандомайзер 
Д/З
- Прочитать и попробовать всё, что вам покажется релевантным задаче из GNU coreutils, возможно, больше 
- В репозитории с Д/З сделать (вложенный) подкаталог 03_Text, а в нём — сценарий на shell randomize.sh, который принимает один необязательный параметр — время задержки в секундах (вещественное) перед выводом очередного символа, затем читает со стандартного ввода небольшой файл с ASCII-артом в кодировке ASCII и без символов табуляции, после чего выводит его посимвольно в случайном порядке в левый верхний угол экрана. Должен получиться с виду тот же ASCII-арт (аккуратнее с пробелами). - Условие: нельзя пользоваться другими языками программирования, кроме shell (sed-ом можно, но можно обойтись и без него). 
 Необязательное дополнение: скрипт должен выполняться стандартным shell-ом (не bash, а dash, он же ash, например). Я пока нашёл одну неприятность; встроенное echo в ash не умеет -e — пользуйтесь /bin/echo, оно умеет Необязательное дополнение: скрипт должен выполняться стандартным shell-ом (не bash, а dash, он же ash, например). Я пока нашёл одну неприятность; встроенное echo в ash не умеет -e — пользуйтесь /bin/echo, оно умеет
 Необязательное дополнение: научиться работать с псевдографикой и русскими буквами. Необязательное дополнение: научиться работать с псевдографикой и русскими буквами.- Проблема: русские буквы в UTF8 занимают два байта, а псевдографика — вообще три
- Решение: сначала перекодировать поток (iconv) в UCS2, например, там всё будет одинаковое 
 
 Необязательное дополнение: выводить ASCII-арт посередине экрана Необязательное дополнение: выводить ASCII-арт посередине экрана- Проблема (неожиданная): посчитать во входном потоке количество строк и максимальную ширину строки легко, а вот передать эти данные перед тем, как передать остальные данные — просто так не получится - Решение: использовать временный файл (mktemp) — только обязательно удалять его 
 
- Размер экрана — stty (причём поскольку стандартный ввод — это не терминал, то конкретно stty size < /dev/tty) 
 
 
Я решал сложный вариант задачи так (нажмите «Комментарии» в шапке страницы, чтобы прочитать спойлер на русском, осталось только перевести на shell ☺):
Базовое решение короче и проще:
Вариант 2021 года:
Вариант 2023 года (рамочка и фон рисуются с помощью dialog; да, она должна быть на 1 больше, но мне понравилось, что месяц отображается в виде заголовка ☺); всего менее 40 строк кода

Посчитал размер экрана с помощью stty, и высоту / максимальную ширину вывода с помощью wc
Вывел рамочку нужного размера с помощью dialog; подстроил координаты, чтобы вывод попадал в эту рамочку
Установил цвет фона с помощью tput setab (такой же, как у внутренности рамочки -серый)
Преобразовал текст в кодировку UCS2 с помощью iconv
Преобразовал текст в последовательность двухбайтовых восьмеричных чисел с помощью od
(while read) считывал ввод в цикле построчно (в две переменные)
(case) пробелы игнорировал, при переводе строки обнулял X, но увеличивал Y, в остальных случаях увеличивал X
(echo) каждую пару чисел вывел, сопровождая полученными координатами X и Y
Перемешал вывод с помощью shuf
(while read) читал уже перемешанные X, Y и два числа-байта
С помощью tput cup) позиционировал курсор и (echo -ne) выводил два байта символа (пригодилась конструкция «\0…») с перекодировкой обратно в UTF
(sleep) ждал после каждого вывода