Shell & CLI Craft
«У нас всё на Python и Go, bash — это наследие и боль» — позиция, после которой команда тратит полчаса на преобразование, которое awk '{print $2}' | sort | uniq -c | sort -rn делает за пять секунд. Shell — это не костыли, а инструмент композиции: полвека утилиты Unix работают через одинаковые входы и выходы, и их можно собрать в нужный конвейер прямо в терминале, без проекта на двести строк. SRE, который не владеет shell свободно, на дежурстве наполовину слеп: пока он пишет разбор логов на Python, инцидент уже погасили те, кто собрал grep | jq | sort за минуту. Этот лист — про беглость примерно в тридцати ключевых утилитах и про дисциплину выбора между shell и полноценной программой.
Граница: Programming Languages — выбор настоящего языка для сервисов и инструментов; shell — для склейки уже существующих утилит. CI/CD — место, где скрипты живут по правилам прода: версии зафиксированы, линтер прогнан, поведение проверено. Здесь — про ремесло самого shell.
Что должен уметь
Заголовок раздела «Что должен уметь»Главный навык на уровне L4 — понимать, где shell кончается и начинается язык. Я регулярно вижу обе крайности: одни пишут двухсотстрочных монстров на bash с функциями, разбором опций и ловушками (это уже Python с худшим синтаксисом — лучше переписать), другие тянут Python ради однострочника awk '{sum += $1} END {print sum}', и это перебор. Правило, которое работает на практике: три и больше условных веток, или нетривиальная структура данных, или сложная обработка ошибок — уже не shell. Задача «прогнать поток через четыре преобразования» — наоборот, ровно shell, и любой Python здесь проиграет.
L3
- Свободно пользуется базовым набором:
grep(с-r,-E,-l,-c),find,sort,uniq,wc,head,tail,cut,tr,xargs. Собирает однострочные конвейеры для разового анализа, не открывая редактор. - Знает разницу между одинарными и двойными кавычками; пишет
"$var", а не$var; проверяет написанное через ShellCheck.
L4
- Бегло пишет
awkдля обработки колоночных данных — один из самых недооценённых инструментов в индустрии; читает простые замены наsed, не открывая документацию. - Использует
jqдля JSON — стандарт де-факто при разборе ответов API; понимает--arg,select(),map(),to_entries. - Пишет дежурные скрипты с минимальной гигиеной:
set -euo pipefail,trapдля уборки за собой, осмысленные коды возврата, сообщения об ошибках вstderr(>&2).
L5
- Различает интерактивный shell (короткие команды, алиасы, история) и скриптовый (оборонительный стиль) и не путает один с другим.
pipefail,nounsetиerrexit— умолчания для скриптов, а не для терминала. - Знает современные замены и выбирает осознанно:
rgвместоgrep -r,fdвместоfind,batвместоcatдля чтения глазами,dufвместоdf. Понимает, когда новый инструмент оправдан, а когда важнее переносимость по POSIX. - Применяет
xargs -Pдля параллельного запуска,parallel— для случаев посложнее; чувствует момент, когда параллельность в shell перестаёт помещаться в голову и пора уходить в нормальный язык.
L6+
- Выстраивает командную дисциплину: зафиксированные версии утилит в CI и контейнере разработки, общий профиль и алиасы, ShellCheck в конвейере, соглашение об именах для дежурных скриптов.
- Решает стратегически: что живёт в общей библиотеке скриптов (общий
bin/, обвязка вокруг Terraform, выгрузки из мониторинга), а что пора переносить в полноценный сервис.
Материалы
Заголовок раздела «Материалы»- Brian Kernighan, Rob Pike — The Unix Programming Environment (Prentice Hall, 1984). Сорок лет, и не устарела. Глава про фильтры — лучшее введение в композицию утилит. Если выбирать одну книгу — эту.
- Eric S. Raymond — The Art of Unix Programming (Addison-Wesley, 2003; полный текст выложен автором на catb.org). Не учебник по shell, а философия: «write programs that do one thing well», «text streams are universal interface». Полезно, чтобы понять, почему этот инструментарий живёт полвека.
- Cameron Newham — Learning the bash Shell (O’Reilly, 3-е изд., 2005). Канонический справочник именно по bash. Не для чтения подряд, а чтобы посмотреть нужное.
- Dave Taylor, Brandon Perry — Wicked Cool Shell Scripts (No Starch Press, 2-е изд., 2017). Сборник живых скриптов с разбором; удобен как коллекция идиом.
Статьи и доклады
Заголовок раздела «Статьи и доклады»- Google Shell Style Guide. Если выбирать одно руководство по стилю — это. Прагматичное, не теоретическое, каждая рекомендация обоснована. По моим наблюдениям, командные стандарты чаще всего вырастают именно из него.
- ShellCheck Wiki. Не статья, а собрание объяснений типовых ошибок: у каждого предупреждения своя страница с подробным разбором. Лучший способ выучить bash — читать эту вики после первого провала собственного скрипта.
- Robert Mecklenburg — Managing Projects with GNU Make (O’Reilly, 3-е изд., 2004). Make — не shell, но живёт рядом. Глава про автоматизацию командной работы через Makefile вполне практическая.
- The Art of Command Line. Подобранная шпаргалка на GitHub. Хороша для самопроверки: если больше половины содержимого незнакомо — есть что доучить.
Инструменты
Заголовок раздела «Инструменты»- bash — совместимый с POSIX выбор по умолчанию. Если брать один shell для скриптов, то его. Zsh приятнее в интерактиве, но в скриптах побеждает переносимость.
- ShellCheck — линтер. Обязательная проверка перед коммитом и в CI для любого репозитория с
.sh. Ловит подавляющее большинство классических ловушек bash. - jq — стандарт де-факто для JSON в командной строке. По моим наблюдениям, владение
jqна уровнеselect() | map()отличает рядового инженера от старшего в дежурных ситуациях с REST API. - yq — то же для YAML; критично для команд, живущих в Kubernetes: манифесты и значения Helm читаются им же.
- ripgrep / fd / bat / delta — современные замены
grep -r,find,catи просмотра различий в git. По моим наблюдениям, для личного окружения чаще всего берут именно эту четвёрку. - GNU parallel /
xargs -P— параллельный запуск.parallelмощнее, но и порог входа выше;xargs -Pзакрывает почти все повседневные случаи. - Modern Unix — подборка современных альтернатив классическим утилитам. Полезно, чтобы время от времени освежать личный набор.
- Анти-инструмент: трёхсотстрочный скрипт на bash с функциями, разбором опций и ветвлениями. Это уже не shell — лучше переписать на Python или Go.
Best practices
Заголовок раздела «Best practices»Главный публичный кейс — не отдельный инцидент, а феномен jq рядом с kubectl: за десяток лет jq превратился из нишевого инструмента в обязательный навык для всех, кто работает с REST API, а kubectl ... -o json | jq стал самой частой идиомой на дежурстве в Kubernetes. Это пример того, как композиция простых инструментов побеждает встроенную сложность: вместо того чтобы встраивать в kubectl собственный язык запросов, индустрия сошлась на «отдай JSON, дальше сделает jq». Я регулярно вижу, как опытный инженер получает ответ на сложный вопрос про кластер за минуту, а вчерашний новичок полчаса пишет то же самое на Python с клиентской библиотекой. Дело не в том, что новичок плохой. Дело в том, насколько дёшево обходится композиция, когда рука набита.
Дальше — правила, которые я считаю обязательными. set -euo pipefail идёт в начало каждого скрипта: без errexit ошибка в середине ломает инварианты, без nounset опечатка в имени переменной тихо превращается в пустую строку, без pipefail падение в начале конвейера просто теряется. Не нравится такое поведение — напишите защиту явно, но не игнорируйте молча. Переменные всегда в кавычках: "$var", а не $var, иначе пробелы и подстановка имён разносят логику. ShellCheck ловит это сам, надо только его слушать.
И правило трёх условий. Появилось в скрипте три ветки, или нетривиальная структура данных, или обработка ошибок сложнее одной строки — здесь shell перестаёт работать как инструмент, переписывайте на Python или Go. Монстры на триста строк не пишутся за один вечер, они растут годами; здоровая команда успевает остановиться раньше.
awk — самый недооценённый инструмент в индустрии. Огромная часть задач SRE звучит одинаково: взять колоночный вывод, посчитать сумму или уникальные значения, отсортировать по нужной колонке. На Python это десяток строк, в awk '{sum[$1] += $2} END {for (k in sum) print k, sum[k]}' — одна прямо в конвейере. По моим наблюдениям, беглого от небеглого в shell почти всегда отличает именно awk. И речь не о том, чтобы выучить его целиком — язык богатый, утонуть легко, — а о десятке идиом наизусть. День работы, разница на годы.
Интерактивный shell и скриптовый — разные стили. Частая ошибка: писать скрипты так, как набираешь в терминале, — без кавычек, без обработки ошибок, без set -e. Обратная встречается тоже: набирать в терминале оборонительные конструкции и терять на этом скорость. В терминале команды короткие, ошибиться не страшно, история спасёт. В скрипте — pipefail, nounset, явная обработка ошибок, прогон через ShellCheck. Там, где эта разница не проговорена вслух, качество скриптов гуляет от автора к автору.
Общая библиотека дежурных скриптов заменяет «волшебные знания в голове». В зрелой команде в общем репозитории лежат несколько десятков скриптов: показать поды в нездоровом состоянии по всему кластеру, зайти в случайный под по метке, достать последние полсотни трассировок из логов сервиса. Это актив команды, а не чьи-то личные настройки. По моим наблюдениям, разрыв между новичком и старшим инженером в одной команде сокращается на месяцы, если такая библиотека есть и поддерживается. Без неё каждый новый человек заново изобретает свои конвейеры.
Версии утилит для прода фиксируются. Скрипт с awk ведёт себя на BSD не так, как на GNU. То же с sed, grep -P и date. Поэтому набор утилит в CI прибивается образом контейнера, asdf или nix; без этого получаете классику «на ноутбуке работало, в CI сломалось». Это не причуда bash, а та же дисциплина цепочки поставок, только применённая к скриптам.
Связанные листья
Заголовок раздела «Связанные листья»- Programming Languages — выбор настоящего языка для сервисов и инструментов; shell остаётся для склейки и разовых задач. Граница: три условия — переходи в язык.
- CI/CD — скрипты в конвейере живут с зафиксированными версиями, прогоняются через линтер и проверяются. Именно здесь скриптовый стиль критичен.
- Operating Systems — shell — главный интерфейс к системе; знание
/proc,/sysи трассировки системных вызовов — общая зона. - Networking —
tcpdump,ss,dig,curl -v,mtr: вся сетевая диагностика собирается в командной строке, без беглости в неё не войти. - Runbooks — шаги runbook часто состоят из команд shell; их качество — кавычки, обработка ошибок — решает, сработает ли runbook в реальном инциденте.
- Performance & Profiling —
perf,strace,ltrace,bpftraceживут в терминале; профилирование без свободного shell не начнётся. - Toil Tracking — регулярно повторяемые команды — кандидаты в общую библиотеку; учёт ловит сигнал «этот конвейер набирали двадцать раз, пора в
bin/».
Открытые вопросы
Заголовок раздела «Открытые вопросы»Zsh против bash в команде: для личного терминала zsh обычно выигрывает, для общих скриптов лучше bash ради переносимости. Только вот граница, на которой терминальная команда становится скриптом, размыта, и жёсткого ответа у меня нет. Рядом — современные оболочки (TBD): fish, nushell, oil; стоит ли на них переходить командой, общепринятой позиции я не встречал. Третья тема — дизайн собственных утилит (TBD), когда команда пишет свой инструмент на Cobra, Click или argparse: коды возврата, формат вывода, флаги. Это, возможно, отдельный лист на стыке с Programming Languages.
И то, в чём я не разобрался: как правильно тестировать скрипты на shell. Bats и shunit2 существуют, но берут их редко, и на практике у большинства скриптов тестов нет вовсе. Если у вас есть работающая практика — расскажите через PR.