Comments 70
Честно говоря, не представляю сценария, где бы я мог удалить /usr.
Работать только в своём каталоге уже не модно?
А если копнуть, то в bash и синтаксиса и возможностей и всего остального ненамного меньше, чем в любом другом скриптовом языке, и недооценивать его нельзя. Я вот буквально пару лет назад осмелился считать себя в баше сеньором, и то — всегда нахожу подводные пещеры, где я никогда не бывал.
Основная задача баш, в отличие от других языков программирования — работа с ОС и другими программами.
производить отладку
/зануда-off
Хотя, наверное для девляпса это божественное откровение
Более того, вы походя уничижительно задели социальную группу, и человека написавшего неплохой и не самый кстати очевидный материал с примерами. И кстати, даже самые простые материалы имеют право на жизнь т.к могут вызвать живое обсуждение, в котором ответы (возможно) будут не столь очевидны.
з.ы. Высокомерие и желание утвердится за счёт других — как правило компенсаторный механизм.
2. Какой у вас лично уровень, и какие симптомы у вас вызывает этот факт — как-то всё равно. Т.к. вы даже не осознаёте п.1.
Забыли про главную практику написания shell-скриптов, а именно не использовать bash-специфичный синтаксис и пользоваться POSIX shell.
Зачем вообще нужен bash если его фичами нельзя пользоваться? Не говоря уже о том что сейчас сложно найти систему где его нет (разве что MCU), да и статья не о shell-scripts а конкретно о bash.
Все эти заявления на тему "непортабельно" изрядно устарели — если им следовать, то можно вообще прекратить разработку чего-то нового, ибо "не рекомендуется", но я подозреваю что реально совместимость с 99% систем нужна едва-ли в 1% случаев.
Что касается POSIX… то он тоже не без нареканий, если всё притянуть за уши к соответствию, весь сделанный прогресс можно откатить лет на 20 назад, ибо работать в чём-то что строго следует стандарту (и писать под это код) — примерно как бегать с будкой на голове и обмотав ноги цепью.
А где он, кроме ембеда, встречается? В контейнерах? Ну там и баш немудрено поставить.
Ну, хотя бы, потому, что полно UNIX дистрибутивов, где bash нет в составе базовой системы.
И, если писать по-настоящему портабельный софт, то заставлять тащить туда совершенно ненужный bash это, как минимум, моветон.
Ну, хотя бы, потому, что полно UNIX дистрибутивов, где bash нет в составе базовой системы.
Насчёт UNIX ещё могу поверить, но кому сейчас нужно что-то, кроме Linux? Не-Linux — это маргинальный случай, для которого нужен отдельный набор инструментов, включая скрипты. Поэтому "полно UNIX дистрибутивов" — это явное преувеличение.
А если вы имели в виду Linux-дистрибутивы — было бы интересно узнать, в каких же из них нет bash. Раз уж их "полно", может, назовёте, хотя бы, 3-4?
Разумеется, не Linux. Все *BSD, например или линейка Solaris и её наследников.
Насчёт Linux не скажу, поскольку мне не слишком интересно ковыряться в этой куче.
Ну и, про Busybox вам уже написали.
Ок, сам 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?
В статье не указано ни про портабельный софт ни про линукс шелл. Написано прямо про bash.
Писать на POSIX shell надо конкретно примерно всегда.
Если вдруг у вас возникает потребность, именно потребность, а не соблазн, использовать bash специфичные возможности, то весьма вероятно то вам пора переходить на другой язык программирования с более богатой функциональностью.
Таков мой опыт проектов на shell (это, кстати говоря, тысячи строк кода).
Презентабельно, если вы расскажете сколько и каких компаний вы сменили, и какие ОС там использовались.
За последние 15 лет, у меня баш или ksh был практически везде, за исключением ембеддед. Сейчас иногда встречается в контейнерах, но тоже нечасто.
Поэтому писать на POSIX shell нужно не конкретно примерно всегда, а тогда когда к этому вынуждает рабочая обстановка.
Переходить на другой язык программирования, когда выгоднее использовать баш — тоже совет так себе.
А вот писать скрипт из тысяч строк кода на баш — на вашем месте я бы задумался про другой язык.
К сожалению, конкретики не будет в связи с NDA. Скажу лишь, что компании не сказать чтобы крупные в мировых масштабах, но в своей сфере и в своём регионе заметные.
Базовая система там одна из мутаций Linux, однако никакой нужды использовать конкретно особенности bash не было вовсе. Широкое использование shell скриптинга связанно с чрезвычайно гетерогенной в силу исторических причин средой, как средство её унификации и сокращения инструментария используемого в backend, а также обеспечения портабельности за переделы экосистемы Linux.
К сожалению, конкретики не будет в связи с NDA
Ох-ох.
Совершенно не проблема сказать название области, не называя компанию, чтобы было понятно почему там такой зоопарк разных *nix систем.
Тем не менее вы подтверждаете, что «Писать на POSIX shell надо конкретно примерно всегда» это ваше личное IMHO базирующееся на вашем личном опыте работы, а не всемирная бест практика.
А, ну и насчёт портабельности: на работе везде RHEL-подобные системы и никаких эзотерических новых дистров не ожидается и не приветствуется. Зачем мне там портабельность? Для понтов?
Лимит на длину строки 140 символов, количество непустых строк не более 600. Вот и все правила, если в них баш не укладывается, пишем на питон/ансибл.
rand_dir_name="$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 16 | head -n 1)"
Зачем такой ужас? Есть же mktemp, который делает ровно то что нужно.
А как же правило "не изобретай велосипед"?
Как минимум по двум пунктам:
- lock фалы использовать через flock
- временные директории создавать через mktemp -d
Вы имели ввиду использовать flock но не на отдельных файлах на PID файлах?
Тогда согласен.
Судя по всему, происходит так:
- Вызываем flock() на будущий pid-файл. Понятно что сначала блокировка, может уже́ инстанс был запущен. Он создаётся.
- В systemd прилетает inotify, он бросается читать pid (а его ещё нету).
- Ругань про невозможность прочитать pid (а тем временем процесс отфоркался, написал в pid что получилось и на следующем заходе всё хорошо).
А почему не подходит Type=simple
безо всяких PID-файлов?
Понимаю, сейчас это не модно. Но бывают случаи, когда нужно как можно скромнее потреблять ресурсы. Для этого когда-то был придуман двухкратный форк самого себя. С избавлением от всего лишнего, включая tty*.
Модель «simple» предполагает что «каким родился — таким и пригодился», а тут вообще всё меняется (кроме ppid).
- Для подобных штук придумали ещё фокус с сигнализацией (STOP самому себе и CONT от поймавшего сигнал супервизора). Но как-то он популярности не получил. Судя по всему, архитектор systemd про это вообще не знал и сделал свой велосипед через dbus.
Но бывают случаи, когда нужно как можно скромнее потреблять ресурсы. Для этого когда-то был придуман двухкратный форк самого себя. С избавлением от всего лишнего, включая tty*.
Звучит как экономия на спичках, неужели это всё ещё востребовано, особенно, там, где есть systemd?
Для обработки таких сценариев важно использовать встроенные функции 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'"
Большое спасибо за статью, хорошие советы, сам многое делаю именно так.
Есть замечание по
rand_dir_name="$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 16 | head -n 1)"
Тут нет проверки что такой каталог уже не используется из другого скрипта что может закончится плачевно.
Есть mktemp который занимается этим (может создать временный каталог\файл)
Ещё я читал, что есть такая переменная $RANDOM.
Тут нет проверки что такой каталог уже не используется из другого скрипта что может закончится плачевно.так mktemp тоже не проверяет, что каталог используется, но с учетом самого имени
$ mktemp
/var/folders/gn/qxngd93x399fjdmtzkqwbvnw0000gn/T/tmp.a0e7TEP5
думаю, что вероятность совпадения стремится к 0
mktemp, в теории, должен попробовать другое имя если сгенерированное уже занято, но это зависит от конкретной версии, наверное.
Плюс, гарантируется что если он завершился успешно, то файл (или директорий) был создан с этим именем (именно создан — т.е. его там не было раньше) — а если сначала генерить имя, а потом "вручную" пытаться создать его — это race condition, пусть и с очень низкой вероятностью (хотя как знать, что там в random, может он глючной).
Но основная суть в том что mktemp проще чем приведённая в статье конструкция, выполняющая ту же функцию.
Тоже хотел оставить эту ссылку, а именно цитату:
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.
ЗЫ: статья в целом хорошая и полезная, но этой цитаты не хватает :)
Баш — это такой язык, в котором надо использовать обширный набор специальных практик по борьбе с идиотскими дефолтами. Те, кто знает их все, а так же большей частью не забывает применять, называют "гуру".
Defensive programming в чистом виде. #pragma warn, любая опечатка может и будет использована для UB и т.д. Garbage in, garbage out.
Проблема с башом не в отдельных скриптах, а в том, что башизмы — это типовой метод склеивания разных кусков CI'я. Начинается невинно — список команд для выполнения. Потом появляется первый if или ||, потом кто-то добавляет строковые операции или полагается на специальную магию, а потом получившийся тонкий слой баша поверх всех остальных абстракций уже читать невозможно.
Чем меньше баша, тем лучше. И под башом я подразумеваю любой шелл sh-типа.
Тем не менее, иногда бывает полезно (и приятно) заменить страницу Ansible-плейбука на shell: >-
из нескольких строчек...
В том месте, где вы используете баш "снизу" он наименее опасен. Потому что как только кто-то перестаёт понимать написанное или оно вызывает вопросы, то shell превращается в кастомный модуль с тестами (как минимум юнит, как максимум — интерграционными с коллекцией на galaxy).
Самый страшный — это баш сверху и в прослойках. Например, если кто-то решил срезать угол и что-то меняет в инвентори. Или ансибл вызывается с прелюбоптнейшими выражениями баша внутри -e
, и т.д.
Для меня code smell проекта — это функция экспоненты от цикломатической сложности баша (включая неявные условия от математики над переменными). 1 — ок, 2 — уже попахивает, 3 — конкретный code smell, 4 — беда-беда, 5 — я такое не читаю и от review такого отказываюсь.
Это не отменяет возможности писать на баше нормально. Мы, например, используем git vendor, который внутри — 230 строк баша, но написанных так хорошо, что напоминают нормальную программу.
Но как смазочный материал — опасно. Часто нужно, но чем меньше, тем лучше.
Давно читал что-то вроде "Если бы все знали bash, sed и awk — то 90 % программ не понадобилось бы" Преувеличение, конечно. Но если добавить vim, screen и ssh то вроде и правда :-)). Глядя на wallix bastion отчетливо понимаешь "Победа сил добра над силами разума".
Некогда для своего же удобства был написан мини пошаговый 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
Лучшие практики bash-скриптов: краткое руководство по надежным и производительным скриптам bash