Программист должен помнить не только о вещах, которые нужно делать. Обратное тоже важно: запоминайте, чего делать не нужно. Плохой опыт — тоже опыт. Он поможет не упасть там, где спотыкаются другие.

Одна из таких вещей — скрипты на баше. Я взял за правило: никаких баш-скриптов. Когда хочется написать на нем что-то длиннее пяти строк, закрывайте ноут и идите за молоком. Потом вернитесь и сделайте правильно — то есть без баша.

Шелл-скрипты хрупки и тяжелы в поддержке. Их трудно читать и отлаживать. Они опираются на сторонние утилиты, которые где-то есть, а где-то нет. Поведение зависит от платформы и флагов.

Шелл — это плавное погружение в смоляную яму, из которой не выбраться. Год-другой — и скрипт занимает экраны, никто не знает, как он работает. Лучше не трогать.

Замечу, что сказанное не относится к случаям, когда вызывают несколько утилит, соединяя их каналы, например:

> ps aux | grep java

Это совершенно нормально. Я имею в виду скрипты, где шелл выступает в роли настоящего языка программирования, то есть:

  • активно используются переменные;
  • циклы, ветвления, переходы;
  • объявление и вызов функций;
  • импорт других скриптов.

Все вместе это становится таким адом, что лучше уволиться, чем ворошить осиное гнездо (или, в английской идиоме — тараканье).

Кусок скрипта откуда-то из интернета:

no_more_args=1 ;
  -*)
   if [ "x$no_more_args" = "x1" ] ;
then
    dirs[$num_dirs]="$1";
    let "num_dirs++"
else
 if [[ "x${1:1:1}" != "x-" && "x$1" =~ "x-.*a.*" ]] ;
 then
     no_dots=0;
 fi
    opts="$opts $1";
   fi
  *)
   dirs[$num_dirs]="$1";
   let "num_dirs++"
 esac
 shift

Для меня он выглядит как китайская грамота. Что такое [ "x$no_more_args" = "x1" ]? Что такое [[ "x${1:1:1}" != "x-" && "x$1" =~ "x-.*a.*" ]]? Как это читать и поддерживать?

Во времена Unix шелл-скрипты были прорывом, с этим никто не спорит. Однако время не стоит на месте, и созданы более удобные инструменты. Вот несколько примеров.

Если с проектом нужно делать много мелких вещей, подойдет make. Напомню, make — это набор именованных действий. Кроме вызова по отдельности их можно комбинировать, например собрать бек и фронт за раз. В любом проекте я завожу Makefile и помещаю туда все, что придет в голову, например:

  • сборку проекта
  • прогон тестов и миграций
  • очистку временных файлов и папок
  • запуск и остановку Докера
  • много чего еще

Пример большого Make-файла можно посмотреть в репозитории с книгой.

Далее: если нужны действительно сложные скрипты, возьмите Питон. Даже на голом Питоне писать в разы легче, чем на баше. Для Питона созданы библиотеки с изящным API, чтобы вызывать процессы, двигать файлы и прочее.

Да, Питон требует окружения. Однако если у вас сложные скрипты, он окупается. Установку Питона и его барахла поместите в Makefile.

Для управления машинами лучше писать плейбуки на YAML. Кроме Ansible, полно других утилит для конфигураций.

Любые действия лучше задавать декларативно. Если вы пишете скрипты, скорей всего, вы не знаете о нужной утилите.

Еще одна интересная мысль — генерация шелла из другого языка. Подобно тому, как из TypeScript производят JavaScript, можно произвести шелл из условного Лиспа или Питона. Когда я работал в Exoscale, у нас была библиотека для генерации шелла на Кложе. Я ей не пользовался, но само наличие говорит о том, что в руководстве понимали риски ручного скриптописания.

Припомню только один случай, когда от шелла не отвертишься — это докер-контейнеры. Кроме голой операционки там ничего нет, поэтому сжимаешь булки и пишешь на шелле. Тянуть мегабайты Питона, конечно, будет плохим вариантом.

В общем, чем раньше вы замените шелл-скрипт на что-то профильное, тем лучше.