Bash скрипты
Используем плагины и инструменты
- Shell Script - IntelliJ IDEs Plugin | Marketplace
- https://explainshell.com/ - вставляем непонятную команду с параметрами, получаем справку по ней и заданным параметрам
🔥 Обязательные требования
Указываем командную оболочку для выполнения скриптов: Bash
#!/usr/bin/env bash
Разн ые оболочки по-разному выполняют команды.
Режим прекращения выполнения в случае ошибки любой команды
set -eo pipefail
Используем $SELF_DIR для относительных путей
SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
RELATIVE_PATH=${RELATIVE_PATH:-"$SELF_DIR/relative/path"}
Задаём переменным значения по-умолчанию
CI_BUILD_NUMBER=${CI_BUILD_NUMBER:-0}
Даём возможность задавать значения параметров снаружи
# ❌ Плохо: нельзя передать другое значение снаружи
XCODE_APP_NAME="application"
# ✅ Хорошо: можно задать любое значение переменной снаружи
XCODE_APP_NAME=${XCODE_APP_NAME:-"application"}
Делим скрипт на логические части при помощи функций
task_foo () {
# Script of task foo
}
task_boo () {
# Script of task boo
}
task_foo
# task_boo - временно отключено
🟢 Хорошие практики и шаблоны
Ветвление по имени команды в первом аргументе
task_foo () {
echo "exec: task_foo"
}
task_boo () {
echo "exec: task_boo first arg: $1"
echo "exec: task_boo all task args: $\*"
}
"$@"
./script.sh task_boo --one --two
exec: task_boo first arg: --one
exec: task_boo all task args: --one --two
Скрипт с подкомандами
#!/usr/bin/env bash
set -eo pipefail
SELF_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")"
clean() {
echo "[INFO] clean"
}
build() {
echo "[INFO] build"
}
deploy() {
echo "[INFO] deploy"
}
default() {
clean
build
deploy
}
if [[ -z "$1" ]]; then
default
elif [[ $(type -t "$1") == function ]]; then
"$@"
else
echo "Unknown script command: $1" && exit 1
fi
✅ Более короткий вариант проверки вызова только функции из скрипта
[[ ! $(type -t "$1") == function ]] && echo "Not specified or invalid script command: $1" && exit 1
Определение путей относительно исполняемого скрипта
BASH_SOURCE[0]
содержит путь, к скрипту, который исполняется.
Он может быть как абсолютным, так и относительным.
В тексте скрипта надёжнее использовать абсолютные пути, которые не зависят от текущей папки.
Чтобы получить абсолютные пути относительно расположения исполняемого скрипта, используем команды перечисленные далее.
SELF_DIR
# Папка расположения исполняемого скрипта
SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
SELF_PATH
# Полный путь расположения исполняемого скрипта
SELF_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")"
Логирование в начала и конца выполнения скрипта
SELF_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")"
echo "($SELF_PATH) begin ..."
# код скрипта
echo "($SELF_PATH) done"
Выполнение поиска бинарника для выполнения вместо явного указания пути
Допустим не известно точно, где расположен бинарник deno, но зато оболочка знает как его найти, тогда используем:
#!/usr/bin/env -S deno run
Параметр -S
указываем обязательно, чтобы строка команды с аргументами была разделена через пробел.
Без -S
она передаётся как один аргумент и как правило происходит ошибка.
См. 23.2.2 -S/--split-string usage in scripts.
Чтение значения параметра из .env фала
Значение может быть без кавычек или в двойных кавычках
CI_BUILD_VERSION_PREFIX=$(sed -nE 's/^CI_BUILD_VERSION_PREFIX\="?([^"]*)"?$/\1/p' "$SELF_DIR/common.env")
CI_BUILD_VERSION_PREFIX=1.0.0
# или
CI_BUILD_VERSION_PREFIX="1.0.0"
Скрипт вызван на выполнение или подключен через source
#!/bin/bash
# Определить какое-то значение
get_some_value() {
echo "Это значение, которое надо было определить"
}
if [ "$0" = "${BASH_SOURCE[0]}" ]; then
# ✅ Тут функция будет вызвана, только если скрипт запушен на выполнение
# ⭕️ Не будет вызвана, если скрипт подключен через \`source\`
get_some_value
fi
# ✅ Получение и отображение в логе значения, вызовом скрипта
./get_some_value.sh
# ✅ Получение значение, вызовом функции из скрипта
source ./get_some_value.sh
if [[ "\<условие>" ]]; then
some_value
fi
Короткий if/else для определения значения (аналог тернарного if ? then : else)
local exclude_platform
[[ $PLATFORM = "ios" ]] && exclude_platform="android" || exclude_platform="ios"
Прочитать значение поля из JSON файла
local version
version=$(node -p "require('./package.json').version")local version
version=$(deno eval -p "(await import('./package.json', {assert: {type: 'json'}})).default.version")
Проверить есть ли скрипт в package.json scripts
local mycustomscript
mycustomscript=$(node -p "require('./package.json').scripts.mycustomscript || ''")
if [[ -n "$mycustomscript" ]]; then
npm run mycustomscript
fi
Задать значение переменной окружения по-умолчанию в зависимости от значения другой переменной окружения
export RUBY_SETUP=${RUBY_SETUP:-$([ "$PLATFORM" = "ios" ] && echo "true")}
export GEMS_INSTALL=${GEMS_INSTALL:-$([ "$PLATFORM" = "ios" ] && echo "true")}