Ошибки пайплайна: когда я путаю bash и sh
Пишу скрипты локально в zsh/bash, они отлично работают, кладу в .gitlab-ci.yml —
и job падает с синтаксической ошибкой на ровном месте. Причина одна и та же:
дефолтный шелл в job'ах GitLab — /bin/sh (dash в Debian/Ubuntu-образах),
а не bash. Вот мои типовые промахи.
1. Двойные скобки и условия
if [ "$CI_COMMIT_BRANCH" == "main" ]; then # ✗ sh: == не работает, только =
В sh классика — одинарные = в [ ]. [[ ]] — вообще bash-only.
2. source вместо точки
source .env && ./deploy.sh # ✗ sh: source: not found
. ./.env && ./deploy.sh # ✓ так работает везде
3. Подстановки и массивы
files=(dist/*.js) # ✗ массивы — bash
echo ${files[0]} # ✗ и это тоже
echo "привет" | read x # ✗ в sh read выполняется в подшелле, x потеряется
В sh вместо массива — for f in dist/*.js, вместо read из пайпа —
x=$(echo ...) или here-string.
4. echo -e и прочие «улучшайзинги»
echo -e "строка\nстрока" # ✗ в dash флага -e нет, напечатает букву n
printf "строка\nстрока" # ✓ printf есть везде и ведёт себя одинаково
5. local, pipefail и arithmetic
f() { local x=1; } # ✗ local — bashism
set -o pipefail # ✗ dash не знает pipefail
i=$((i+1)) # ✓ а вот это POSIX и работает
Что я делаю теперь
Три правила, которые выработал:
- По умолчанию пишу POSIX-sh. Точка вместо source,
=вместо==, printf вместо echo -e. Это работает и в sh, и в bash, и в zsh. - Если уж нужен bash — объявляю явно, а не надеюсь на дефолт:
Ещё надёжнее —myjob: before_script: - apk add bash # или apt-get install -y bash script: - bash -c '... [[ ... ]] ...'script: - bash my-script.shс шебангом#!/usr/bin/env bashи хранить скрипт отдельным файлом, а не в YAML: и diff'ится лучше, и локально гоняется тем жеbash script.sh. - Проверяю локально через sh прямо перед коммитом:
sh -n .ci/script.sh— синтаксис, илиdocker run --rm -v $PWD:/w -w /w alpine sh script.sh— прогон именно в dash.
И маленький бонус: set -e в sh работает, но ведёт себя иначе вокруг пайпов
и условий — если пользуюсь &&/|| цепочками, set -e вообще не добавляю,
чтобы не гадать, где оно сработает.