Перейти к содержимому

«У нас всё на 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.

Главный публичный кейс — не отдельный инцидент, а феномен 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 и трассировки системных вызовов — общая зона.
  • Networkingtcpdump, ss, dig, curl -v, mtr: вся сетевая диагностика собирается в командной строке, без беглости в неё не войти.
  • Runbooks — шаги runbook часто состоят из команд shell; их качество — кавычки, обработка ошибок — решает, сработает ли runbook в реальном инциденте.
  • Performance & Profilingperf, 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.