company_banner

Лучшие практики bash-скриптов: краткое руководство по надежным и производительным скриптам bash

Автор оригинала: Akshay Kapoor
  • Перевод

Shell wallpaper by manapi

Отладка сценариев bash — это как поиск иголки в стоге сена, тем более, когда новые дополнения появляются в существующей кодовой базе без своевременного рассмотрения вопросов структуры, логирования и надежности. В таких ситуациях можно оказаться как из-за собственных ошибок, так и при управлении сложными нагромождениями скриптов.

Команда Mail.ru Cloud Solutions перевела статью с рекомендациям, благодаря которым вы сможете лучше писать, отлаживать и поддерживать свои сценарии. Хотите верьте, хотите нет, но ничто не может сравниться с удовлетворением от написания чистого, готового к использованию bash-кода, который работает каждый раз.

В статье автор делится тем, что узнал за последние несколько лет, а также некоторыми распространенными ошибками, которые заставали его врасплох. Это важно, потому что каждый разработчик программного обеспечения в определенный момент своей карьеры работает со сценариями для автоматизации рутинных рабочих задач.

Обработчики ловушек


Большинство скриптов bash, с которыми я сталкивался, никогда не использовали эффективный механизм очистки, когда во время выполнения скрипта происходит что-то неожиданное.

Неожиданности могут возникнуть извне, например получение сигнала от ядра. Обработка таких случаев чрезвычайно важна для того, чтобы сценарии были достаточно надежными для запуска в продакшен-системах. Я часто использую обработчики выхода, чтобы реагировать на такие сценарии:

function handle_exit() {
  // Add cleanup code here
  // for eg. rm -f "/tmp/${lock_file}.lock"
  // exit with an appropriate status code
}
  
// trap <HANDLER_FXN> <LIST OF SIGNALS TO TRAP>
trap handle_exit 0 SIGHUP SIGINT SIGQUIT SIGABRT SIGTERM

trap — это встроенная команда оболочки, помогающая вам зарегистрировать функцию очистки, которая вызывается в случае каких-либо сигналов. Однако следует соблюдать особую осторожность с такими обработчиками, как SIGINT, который вызывает прерывание сценария.

Кроме того, в большинстве случаев следует ловить только EXIT, но идея в том, что вы действительно можете настроить поведение скрипта для каждого отдельного сигнала.

Встроенные функции set — быстрое завершение при ошибке


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

rm -rf ${directory_name}/*

Обратите внимание, что переменная directory_name не определена.

Для обработки таких сценариев важно использовать встроенные функции set, такие как set -o errexit, set -o pipefail или set -o nounset в начале скрипта. Эти функции гарантируют, что ваш скрипт завершит работу, как только он встретит любой ненулевой код завершения, использование неопределенных переменных, неправильные команды, переданные по каналу и так далее:

#!/usr/bin/env bash

set -o errexit
set -o nounset
set -o pipefail

function print_var() {
  echo "${var_value}"
}

print_var

$ ./sample.sh
./sample.sh: line 8: var_value: unbound variable

Примечание: встроенные функции, такие как set -o errexit, выйдут из скрипта, как только появится «необработанный» код возврата (кроме нуля). Поэтому лучше ввести пользовательскую обработку ошибок, например:

#!/bin/bash
error_exit() {
  line=$1
  shift 1
  echo "ERROR: non zero return code from line: $line -- $@"
  exit 1
}
a=0
let a++ || error_exit "$LINENO" "let operation returned non 0 code"
echo "you will never see me"
# run it, now we have useful debugging output
$ bash foo.sh
ERROR: non zero return code from line: 9 -- let operation returned non 0 code

Подобное написание скриптов заставляет вас внимательнее относиться к поведению всех команд в скрипте и предусматривать возможность возникновения ошибки прежде, чем она застанет врасплох.

ShellCheck для выявления ошибок во время разработки


Стоит интегрировать что-то вроде ShellCheck в ваши конвейеры разработки и тестирования, чтобы проверять ваш код bash на применение лучших практик.

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

Использование своих exit-кодов


Коды возврата в POSIX — это не просто ноль или единица, а ноль или ненулевое значение. Используйте эти возможности для возврата пользовательских кодов ошибок (между 201-254) для различных случаев ошибок.

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

#!/usr/bin/env bash

SUCCESS=0
FILE_NOT_FOUND=240
DOWNLOAD_FAILED=241

function read_file() {
  if ${file_not_found}; then
    return ${FILE_NOT_FOUND}
  fi
}

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

Функции-логгеры


Красивое и структурированное ведение логов важно, чтобы легко понять результаты выполнения вашего скрипта. Как и в других языках программирования высокого уровня, я всегда использую в моих скриптах bash собственные функции логирования, такие как __msg_info, __msg_error и так далее.

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

#!/usr/bin/env bash

function __msg_error() {
    [[ "${ERROR}" == "1" ]] && echo -e "[ERROR]: $*"
}

function __msg_debug() {
    [[ "${DEBUG}" == "1" ]] && echo -e "[DEBUG]: $*"
}

function __msg_info() {
    [[ "${INFO}" == "1" ]] && echo -e "[INFO]: $*"
}

__msg_error "File could not be found. Cannot proceed"

__msg_debug "Starting script execution with 276MB of available RAM"

Я обычно стараюсь иметь в своих скриптах какой-то механизм __init, где такие переменные логгера и другие системные переменные инициализируются или устанавливаются в значения по умолчанию. Эти переменные также могут устанавливаться из параметров командной строки во время вызова скрипта.

Например, что-то вроде:

$ ./run-script.sh --debug

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

Я обычно основываю выбор, что инициализировать, а что нет, на компромиссе между пользовательским интерфейсом и деталями конфигураций, в которые пользователь может/должен вникнуть.

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


Модульный / многоразовый код


├── framework
│   ├── common
│   │   ├── loggers.sh
│   │   ├── mail_reports.sh
│   │   └── slack_reports.sh
│   └── daily_database_operation.sh

Я держу отдельный репозиторий, который можно использовать для инициализации нового проекта/скрипта bash, который хочу разработать. Всё, что можно использовать повторно, может быть сохранено в репозитории и получено в других проектах, которые хотят использовать такие функциональные возможности. Такая организация проектов значительно уменьшает размер других скриптов, а также гарантирует, что кодовая база мала и легко тестируема.

Как и в приведенном выше примере, все функции ведения логов, такие как __msg_info, __msg_error и другие, например отчеты по Slack, содержатся отдельно в common/* и динамически подключаются в других сценариях, вроде daily_database_operation.sh.

Оставьте после себя чистую систему


Если вы загружаете какие-то ресурсы во время выполнения сценария, рекомендуется хранить все такие данные в общем каталоге со случайным именем, например /tmp/AlRhYbD97/*. Вы можете использовать генераторы случайного текста для выбора имени директории:

rand_dir_name="$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 16 | head -n 1)"

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

Использование lock-файлов


Часто нужно обеспечить выполнение только одного экземпляра сценария на хосте в любой момент времени. Это можно сделать с помощью lock-файлов.

Я обычно создаю lock-файлы в /tmp/project_name/*.lock и проверяю их наличие в начале скрипта. Это помогает корректно завершить работу скрипта и избежать неожиданных изменений состояния системы другим сценарием, работающим параллельно. Lock-файлы не нужны, если вам необходимо, чтобы один и тот же скрипт выполнялся параллельно на данном хосте.

Измерить и улучшить


Нам часто приходится работать со сценариями, которые выполняются в течение длительного периода времени, например ежедневными операциями с базами данных. Такие операции обычно включают в себя последовательность шагов: загрузка данных, проверка на наличие аномалий, импорт данных, отправка отчетов о состоянии и так далее.

В таких случаях я всегда стараюсь разбивать сценарий на отдельные маленькие скрипты и сообщать об их состоянии и времени выполнения с помощью:

time source "${filepath}" "${args}">> "${LOG_DIR}/RUN_LOG" 2>&1

Позже я могу посмотреть время выполнения с помощью:

tac "${LOG_DIR}/RUN_LOG.txt" | grep -m1 "real"

Это помогает мне определить проблемные/медленные области в скриптах, которые нуждаются в оптимизации.

Удачи!

Что еще почитать:

  1. Go и кеши GPU.
  2. Пример event-driven приложения на основе вебхуков в объектном S3-хранилище Mail.ru Cloud Solutions.
  3. Наш телеграм-канал о цифровой трансформации.
Mail.ru Group
Строим Интернет

Комментарии 69

    +19
    Хорошая, годная статья. Но одна из лучших практик написания на bash — минимизировать использование bash.
      +6
      Думаю это крайность, я бы переиначил так: Нужно иметь в голове для скриптов верхнюю границу их сложности, после которой лучше использовать что-нибудь другое.
        0
        В том то и проблема, что граница эта очень условная. Ибо, как показывает практика, отстрелить себе ногу или удалить /usr можно легко и непринужденно одной очевидной на первый взгляд строчкой, при этом, чтобы нормально покрыть ее защитной логикой требуется написать кучу вырвиглазой обвязки.
          +3

          Честно говоря, не представляю сценария, где бы я мог удалить /usr.


          Работать только в своём каталоге уже не модно?

            0

            Модно, но иногда люди делают sudo make install и аналогичные вещи. Был уже один такой баг, не помню в каком, но популярном пакете.


            Впрочем, случайно удалить свой домашний каталог тоже то ещё удовольствие.

          +4
          Эти проблемы появляются тогда, когда много пишешь на разных php/js/java/gо и др. А потом приходишь в bash и думаешь, я ж сеньор девелопер, что я тут в баше за 2 минуты не разберусь?

          А если копнуть, то в bash и синтаксиса и возможностей и всего остального ненамного меньше, чем в любом другом скриптовом языке, и недооценивать его нельзя. Я вот буквально пару лет назад осмелился считать себя в баше сеньором, и то — всегда нахожу подводные пещеры, где я никогда не бывал.
        +7
        Я знаю много опытных NIX админов, кто-то из них знает php, кто-то python, кто-то perl. Но объединяет их одно — все знают bash.
          +4

          Или думают, что знают :-)

          +2
          Если хорошо знаешь bash, то наоборот — лучше использовать его, чем кучу других решений.

          Основная задача баш, в отличие от других языков программирования — работа с ОС и другими программами.
          +1
          А где про «производительные скрипты» написано?
            –8
            отлаживать = корень «лажать»
            производить отладку
            /зануда-off
              –12
              Уровень гайда — КО.
              Хотя, наверное для девляпса это божественное откровение
                +8
                Что заставляет вас ходить в гайды и рассказывать всем что вы это и так знали?
                  –10
                  По Вашему мнения, я не могу высказать свое оценочное суждение о представленном материяле?
                    +8
                    Вы конечно можете, но более чем заслужено огребёте минуса т.к ваш комментарий не несёт никакого конструктива и никакой пользы.

                    Более того, вы походя уничижительно задели социальную группу, и человека написавшего неплохой и не самый кстати очевидный материал с примерами. И кстати, даже самые простые материалы имеют право на жизнь т.к могут вызвать живое обсуждение, в котором ответы (возможно) будут не столь очевидны.

                    з.ы. Высокомерие и желание утвердится за счёт других — как правило компенсаторный механизм.
                      0
                      1. Потому что уровни у всех — разные. Для кого-то эта информация — полезна.
                      2. Какой у вас лично уровень, и какие симптомы у вас вызывает этот факт — как-то всё равно. Т.к. вы даже не осознаёте п.1.
                  0

                  Забыли про главную практику написания shell-скриптов, а именно не использовать bash-специфичный синтаксис и пользоваться POSIX shell.

                    +5

                    Зачем вообще нужен bash если его фичами нельзя пользоваться? Не говоря уже о том что сейчас сложно найти систему где его нет (разве что MCU), да и статья не о shell-scripts а конкретно о bash.


                    Все эти заявления на тему "непортабельно" изрядно устарели — если им следовать, то можно вообще прекратить разработку чего-то нового, ибо "не рекомендуется", но я подозреваю что реально совместимость с 99% систем нужна едва-ли в 1% случаев.


                    Что касается POSIX… то он тоже не без нареканий, если всё притянуть за уши к соответствию, весь сделанный прогресс можно откатить лет на 20 назад, ибо работать в чём-то что строго следует стандарту (и писать под это код) — примерно как бегать с будкой на голове и обмотав ноги цепью.

                      0
                      BusyBox. Он включает некоторое количество «фишек» из Bash, но далеко не все. Так что меру знать в их использовании приходится.
                        0

                        А где он, кроме ембеда, встречается? В контейнерах? Ну там и баш немудрено поставить.

                          0
                          Помимо того в busybox сами по себе утилиты урезанные, поэтому даже если шелл-скрипт будет POSIX-совместимый, не факт что он будет нормально работать в таком кастрированном окружении, где все аналоги ГНУшных утилит имеют только 3-5 основных опций.
                            0
                            Вы так говорите «кроме ембеда», будто ембед можно просто отбросить
                              0

                              А в ембеде нужны переносимые скрипты?

                                0

                                а почему нет?
                                это банально удобно: писать на одном языке для разных устройств. писать/отлаживать на своей рабочей станции, а запускать потом в других местах. использовать один и тот же код в разных местах, наконец.

                          0

                          Ну, хотя бы, потому, что полно UNIX дистрибутивов, где bash нет в составе базовой системы.
                          И, если писать по-настоящему портабельный софт, то заставлять тащить туда совершенно ненужный bash это, как минимум, моветон.

                            0
                            Ну, хотя бы, потому, что полно UNIX дистрибутивов, где bash нет в составе базовой системы.

                            Насчёт UNIX ещё могу поверить, но кому сейчас нужно что-то, кроме Linux? Не-Linux — это маргинальный случай, для которого нужен отдельный набор инструментов, включая скрипты. Поэтому "полно UNIX дистрибутивов" — это явное преувеличение.


                            А если вы имели в виду Linux-дистрибутивы — было бы интересно узнать, в каких же из них нет bash. Раз уж их "полно", может, назовёте, хотя бы, 3-4?

                              0

                              Разумеется, не Linux. Все *BSD, например или линейка Solaris и её наследников.
                              Насчёт Linux не скажу, поскольку мне не слишком интересно ковыряться в этой куче.
                              Ну и, про Busybox вам уже написали.

                                0

                                Ок, сам Solaris можно сразу вычеркнуть:


                                GNU Bourne-Again Shell (bash) (/usr/bin/bash)
                                Bash is the default shell for users in Oracle Solaris.

                                Наследники уже тоже все почили.


                                Про *BSD я совсем забыл. Ок, самый популярный из них — Darwin, т.е. macOS, шёл с bash, в последнем релизе перешли на zsh.


                                Ок, остаются FreeBSD, OpenBSD, NetBSD (ну и DragonFlyBSD) с дефолтными /bin/sh и /bin/tcsh. Ну да, отличные системы, очень их уважаю и ценю, только вот что за скрипты должны быть переносимы между ними и Linux?

                                  0
                                  Даже если на посикс-шелле писать, они всё равно не будут переносимыми, потому что опции бздяшных стандартных утилит могут отличаться от их гнушных (надо сказать превосходящих) аналогов.
                              +2
                              Напишите статью про posix shell, и где конкретно лучше писать именно так.

                              В статье не указано ни про портабельный софт ни про линукс шелл. Написано прямо про bash.
                                0

                                Писать на POSIX shell надо конкретно примерно всегда.
                                Если вдруг у вас возникает потребность, именно потребность, а не соблазн, использовать bash специфичные возможности, то весьма вероятно то вам пора переходить на другой язык программирования с более богатой функциональностью.
                                Таков мой опыт проектов на shell (это, кстати говоря, тысячи строк кода).

                                  +2
                                  Тысячи кода это не презентабельно совершенно.
                                  Презентабельно, если вы расскажете сколько и каких компаний вы сменили, и какие ОС там использовались.

                                  За последние 15 лет, у меня баш или ksh был практически везде, за исключением ембеддед. Сейчас иногда встречается в контейнерах, но тоже нечасто.

                                  Поэтому писать на POSIX shell нужно не конкретно примерно всегда, а тогда когда к этому вынуждает рабочая обстановка.

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

                                  А вот писать скрипт из тысяч строк кода на баш — на вашем месте я бы задумался про другой язык.
                                    –1

                                    К сожалению, конкретики не будет в связи с NDA. Скажу лишь, что компании не сказать чтобы крупные в мировых масштабах, но в своей сфере и в своём регионе заметные.
                                    Базовая система там одна из мутаций Linux, однако никакой нужды использовать конкретно особенности bash не было вовсе. Широкое использование shell скриптинга связанно с чрезвычайно гетерогенной в силу исторических причин средой, как средство её унификации и сокращения инструментария используемого в backend, а также обеспечения портабельности за переделы экосистемы Linux.

                                      +2
                                      К сожалению, конкретики не будет в связи с NDA

                                      Ох-ох.
                                      Совершенно не проблема сказать название области, не называя компанию, чтобы было понятно почему там такой зоопарк разных *nix систем.

                                      Тем не менее вы подтверждаете, что «Писать на POSIX shell надо конкретно примерно всегда» это ваше личное IMHO базирующееся на вашем личном опыте работы, а не всемирная бест практика.
                                        0
                                        Совершенно не проблема сказать название области, не называя компанию, чтобы было понятно почему там такой зоопарк разных *nix систем.

                                        Связь, если говорить в общем. Компания старая.

                                          0
                                          В одной очень старой компании, которая буквально основала связь, я работал. И там был ksh
                                0

                                А, ну и насчёт портабельности: на работе везде RHEL-подобные системы и никаких эзотерических новых дистров не ожидается и не приветствуется. Зачем мне там портабельность? Для понтов?

                            +3

                            Лимит на длину строки 140 символов, количество непустых строк не более 600. Вот и все правила, если в них баш не укладывается, пишем на питон/ансибл.

                              +1

                              О да… Расскажите про это автору osync — 6503 строчки на bash, даже если убрать комментарии и пустые (их там мало), то всё равно наберется около 6000.

                                –2

                                Ну кто-то захотел сделать велосипед, это полезно для практики (lsync может выполнять всю ту же работу и написан на си). В easyrsa 1700 строк. Но это же не значит, что так надо делать? У нас вот те правила, что я выше описал.

                              +9
                              rand_dir_name="$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 16 | head -n 1)"

                              Зачем такой ужас? Есть же mktemp, который делает ровно то что нужно.

                                0
                                Полностью солидарен.
                                mktemp решает все эти вопросы легко и элегантно.
                                  +1
                                  Более того, рандом имеет ненулевую вероятность наткнуться на существующий каталог, а mktemp гарантирует, что этого не произойдёт.
                                  +10

                                  А как же правило "не изобретай велосипед"?


                                  Как минимум по двум пунктам:


                                  1. lock фалы использовать через flock
                                  2. временные директории создавать через mktemp -d
                                    –1
                                    Лучше вместо лок файлов использовать PID файлы
                                      +3

                                      Вы имели ввиду использовать flock но не на отдельных файлах на PID файлах?
                                      Тогда согласен.

                                        0
                                        Но есть нюанс: данный олдскульный хак (как результат, всего один fd у демонизированного процесса, если ему других файлов не надо) при запуске посредством unit-файла в экосистеме одного известного not-a-bug'а приводит к ругани в syslogjournald.
                                        Судя по всему, происходит так:
                                        1. Вызываем flock() на будущий pid-файл. Понятно что сначала блокировка, может уже́ инстанс был запущен. Он создаётся.
                                        2. В systemd прилетает inotify, он бросается читать pid (а его ещё нету).
                                        3. Ругань про невозможность прочитать pid (а тем временем процесс отфоркался, написал в pid что получилось и на следующем заходе всё хорошо).
                                          0

                                          А почему не подходит Type=simple безо всяких PID-файлов?

                                            0
                                            Потому что демон?
                                            Понимаю, сейчас это не модно. Но бывают случаи, когда нужно как можно скромнее потреблять ресурсы. Для этого когда-то был придуман двухкратный форк самого себя. С избавлением от всего лишнего, включая tty*.
                                            Модель «simple» предполагает что «каким родился — таким и пригодился», а тут вообще всё меняется (кроме ppid).
                                            • Для подобных штук придумали ещё фокус с сигнализацией (STOP самому себе и CONT от поймавшего сигнал супервизора). Но как-то он популярности не получил. Судя по всему, архитектор systemd про это вообще не знал и сделал свой велосипед через dbus.
                                              +1
                                              Но бывают случаи, когда нужно как можно скромнее потреблять ресурсы. Для этого когда-то был придуман двухкратный форк самого себя. С избавлением от всего лишнего, включая tty*.

                                              Звучит как экономия на спичках, неужели это всё ещё востребовано, особенно, там, где есть systemd?

                                                –1
                                                Отсутствие мыслей об экономии обычно заканчивается пофигизмом, несоблюдением элементарных стандартов и докером в продакшене.
                                    +4
                                    Для обработки таких сценариев важно использовать встроенные функции set, такие как set -o errexit, set -o pipefail или set -o nounset в начале скрипта.

                                    Это must have в любом скрипте, ещё удобно использовать сокращённый вариант: set -euo pipefail.


                                    Стоит интегрировать что-то вроде ShellCheck в ваши конвейеры разработки и тестирования, чтобы проверять ваш код bash на применение лучших практик.

                                    Ещё для самой базовой проверки синтаксиса можно использовать bash -n:


                                    $ echo 'if then else fi' > badscript.sh
                                    $ bash -n badscript.sh 
                                    badscript.sh: line 1: syntax error near unexpected token `then'
                                    badscript.sh: line 1: `if then else fi'
                                    [2]$ 

                                    Как и в других языках программирования высокого уровня, я всегда использую в моих скриптах bash собственные функции логирования, такие как __msg_info, __msg_error и так далее.

                                    Я обычно делаю проще:


                                    info() { >&2 echo -e  " INFO: $*"; }
                                    die() { >&2 echo -e "ERROR: $*"; exit 1; }

                                    Тогда всякие вспомогательные проверочки можно писать в лаконичном декларативном стиле, чем-то напоминающим Perl:


                                    #/bin/bash
                                    
                                    set -euo pipefail
                                    info() { >&2 echo -e  " INFO: $*"; }
                                    die() { >&2 echo -e "ERROR: $*"; exit 1; }
                                    
                                    info "Checking the environment variables"
                                    [[ -n ${MY_DIR:-} ]] \
                                      || die "'MY_DIR' is not defined"
                                    
                                    [[ -d $MY_DIR ]] || mkdir -p $MY_DIR \
                                      || die "Could not create the directory '$MY_DIR'"
                                      +1

                                      Большое спасибо за статью, хорошие советы, сам многое делаю именно так.
                                      Есть замечание по


                                      rand_dir_name="$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 16 | head -n 1)"

                                      Тут нет проверки что такой каталог уже не используется из другого скрипта что может закончится плачевно.
                                      Есть mktemp который занимается этим (может создать временный каталог\файл)

                                        0

                                        Ещё я читал, что есть такая переменная $RANDOM.

                                          0
                                          Тут нет проверки что такой каталог уже не используется из другого скрипта что может закончится плачевно.
                                          так mktemp тоже не проверяет, что каталог используется, но с учетом самого имени
                                          $ mktemp
                                          /var/folders/gn/qxngd93x399fjdmtzkqwbvnw0000gn/T/tmp.a0e7TEP5
                                          

                                          думаю, что вероятность совпадения стремится к 0
                                            +1

                                            mktemp, в теории, должен попробовать другое имя если сгенерированное уже занято, но это зависит от конкретной версии, наверное.


                                            Плюс, гарантируется что если он завершился успешно, то файл (или директорий) был создан с этим именем (именно создан — т.е. его там не было раньше) — а если сначала генерить имя, а потом "вручную" пытаться создать его — это race condition, пусть и с очень низкой вероятностью (хотя как знать, что там в random, может он глючной).


                                            Но основная суть в том что mktemp проще чем приведённая в статье конструкция, выполняющая ту же функцию.

                                          +2
                                          Оставлю это здесь. google.github.io/styleguide/shellguide.html
                                            +1

                                            Тоже хотел оставить эту ссылку, а именно цитату:


                                            Shell should only be used for small utilities or simple wrapper scripts.

                                            и


                                            If you are writing a script that is more than 100 lines long, or that uses non-straightforward control flow logic, you should rewrite it in a more structured language now. Bear in mind that scripts grow. Rewrite your script early to avoid a more time-consuming rewrite at a later date.

                                            ЗЫ: статья в целом хорошая и полезная, но этой цитаты не хватает :)

                                            0
                                            Полезно, учту.
                                              0
                                              Вроде как надо использовать lock-директории, а не lock-файлы. Т.к. mkdir вернет ошибку, если директория уже создана. С файлами такого сделать не получится.
                                                +2

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


                                                Defensive programming в чистом виде. #pragma warn, любая опечатка может и будет использована для UB и т.д. Garbage in, garbage out.


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


                                                Чем меньше баша, тем лучше. И под башом я подразумеваю любой шелл sh-типа.

                                                  0

                                                  Тем не менее, иногда бывает полезно (и приятно) заменить страницу Ansible-плейбука на shell: >- из нескольких строчек...

                                                    +2

                                                    В том месте, где вы используете баш "снизу" он наименее опасен. Потому что как только кто-то перестаёт понимать написанное или оно вызывает вопросы, то shell превращается в кастомный модуль с тестами (как минимум юнит, как максимум — интерграционными с коллекцией на galaxy).


                                                    Самый страшный — это баш сверху и в прослойках. Например, если кто-то решил срезать угол и что-то меняет в инвентори. Или ансибл вызывается с прелюбоптнейшими выражениями баша внутри -e, и т.д.


                                                    Для меня code smell проекта — это функция экспоненты от цикломатической сложности баша (включая неявные условия от математики над переменными). 1 — ок, 2 — уже попахивает, 3 — конкретный code smell, 4 — беда-беда, 5 — я такое не читаю и от review такого отказываюсь.


                                                    Это не отменяет возможности писать на баше нормально. Мы, например, используем git vendor, который внутри — 230 строк баша, но написанных так хорошо, что напоминают нормальную программу.


                                                    Но как смазочный материал — опасно. Часто нужно, но чем меньше, тем лучше.

                                                      0

                                                      BTW, про тестирование в ansible не хотите статью написать?

                                                  0

                                                  Давно читал что-то вроде "Если бы все знали bash, sed и awk — то 90 % программ не понадобилось бы" Преувеличение, конечно. Но если добавить vim, screen и ssh то вроде и правда :-)). Глядя на wallix bastion отчетливо понимаешь "Победа сил добра над силами разума".

                                                    0
                                                    Просто пусть полежит здесь.
                                                    Некогда для своего же удобства был написан мини пошаговый db для bash. Просто красивый вывод исполняемой строки с глубиной вложения и с возможностью продолжения по нажатию клавиши.

                                                    debug_handler()
                                                    {
                                                        local dkey="$1"
                                                        echo "[DEBUG[]]"
                                                    }
                                                    
                                                    PS4='+ ${BASH_SOURCE##*/}:${LINENO}:${FUNCNAME[0]-main}():[${SHLVL},${BASH_SUBSHELL},e=$?]`[ $DEBUG_BY_LINE ] && read -s -N 1 -r DBGKEY && debug_handler "$DBGKEY"`# '
                                                    


                                                    могло включаться в любом месте файла, который надо отладить просто добавлением DEBUG_BY_LINE=1
                                                    source ./debug
                                                    # .....
                                                    DEBUG_BY_LINE=1
                                                    # ... то что нужно отладить
                                                    DEBUG_BY_LINE=0
                                                    
                                                      0
                                                      Как по мне — то это пример write-only кода. То есть такого кода, который можно написать, но прочитать (и понять) уже практически никто не может, зачастую даже и сам автор.
                                                        +1
                                                        Да не, тут все понятно, но я бы сказал что слишком оверхед для дебага.

                                                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                    Самое читаемое