Зачем DE bash-скрипты
Даже в эпоху Python, Spark, Airflow и Kubernetes, bash остаётся главным языком склейки для DE-задач. Тебе как Junior DE bash понадобится для:
- Обёртки для python ETL:
run.shкоторый активирует venv, экспортирует env, запускает Python с правильными флагами. - Cleanup-скрипты: ротация логов, удаление temp-файлов, vacuum старых партиций (см. модуль
13-disk-filesystems). - Deploy-скрипты: rsync файлов, перезапуск сервисов, проверка health.
- Cron / systemd timer jobs: один скрипт, который запускается по расписанию (модуль 16).
- CI/CD glue: на CI bash склеивает: docker build -> tests -> push -> deploy.
- Quick одностроки: «один раз обработать CSV», «найти все .parquet без ID-колонки».
Bash 5.3 (Ubuntu 26.04 LTS, Debian 13 Trixie) — мощный современный язык. Не для production-pipeline (там лучше Python), но для clue-кода незаменим.
Этот урок — твой первый bash-скрипт: shebang, права, запуск, exit codes. Дальше в модуле — переменные, аргументы, условия, циклы.
Анатомия минимального скрипта
#!/usr/bin/env bash
# Этот скрипт говорит hello
echo "Hello, $USER!"
Три строки, каждая — со смыслом:
#!/usr/bin/env bash— shebang. Говорит kernel-у, чем запускать этот файл.# Этот скрипт говорит hello— комментарий. Всё после#(не внутри строки) до конца строки.echo "Hello, $USER!"— команда.echoпечатает,$USERподставляется как переменная (имя текущего user).
Shebang: что это и зачем
#! (читается «shebang» или «hashbang») — особая последовательность в начале файла. Kernel при execve() читает первые два байта; если они #!, kernel запускает интерпретатор из shebang.
$ cat hello.sh
#!/usr/bin/env bash
echo "Hello!"
$ chmod +x hello.sh
$ ./hello.sh
Hello!
Что происходит при ./hello.sh:
- Shell делает
execve("./hello.sh", ...). - Kernel читает первые байты — видит
#!/usr/bin/env bash. - Запускает
/usr/bin/env bash ./hello.shпод капотом. env bashнаходит bash в PATH и передаёт скрипт ему.
Без shebang скрипт всё равно может выполниться, но только если запустить через shell: bash hello.sh. Просто ./hello.sh тогда упадёт с exec format error.
#!/usr/bin/env bash vs #!/bin/bash
Вы увидите оба варианта. Разница:
Каждый имеет свою нишу. Современный DE предпочитает env-форму.
Рекомендация: всегда #!/usr/bin/env bash. Это стандарт modern bash community.
Особенно важно для macOS: /bin/bash там — Bash 3.2 от 2007 года (Apple после GPL 2->3 не обновляет). Bash 5.x ставится через brew install bash в /usr/local/bin/bash или /opt/homebrew/bin/bash. #!/usr/bin/env bash найдёт правильную версию.
chmod +x: сделать скрипт исполняемым
По умолчанию текстовый файл не исполняемый:
$ ls -l hello.sh
-rw-r--r-- 1 levo levo 35 May 13 12:34 hello.sh
$ ./hello.sh
bash: ./hello.sh: Permission denied
-rw-r--r-- = 0644 = owner может читать/писать, остальные — только читать. Никто не может выполнять (x).
Чтобы запустить, нужно дать +x (executable bit):
$ chmod +x hello.sh
$ ls -l hello.sh
-rwxr-xr-x 1 levo levo 35 May 13 12:34 hello.sh
$ ./hello.sh
Hello!
Теперь права 0755: owner может всё, остальные — читать и выполнять.
Подробнее про права — модуль 05-permissions-users.
Альтернатива: запуск через интерпретатор
Если не хочешь делать chmod +x:
$ bash hello.sh
Hello!
Здесь ты запускаешь не сам файл (он не executable), а bash, которому передаёшь файл как аргумент. Bash сам читает и интерпретирует — shebang не важен в этом случае.
Это полезно для:
- быстрых ad-hoc скриптов;
- скриптов, которые лежат на read-only ФС;
- debugging:
bash -x script.sh(xtrace mode);
Но для production-скриптов всегда: chmod +x, shebang, запуск как ./script.sh или абсолютным путём.
./script.sh vs script.sh
$ ./hello.sh # запустить из текущей директории
$ hello.sh # bash: hello.sh: command not found
Почему hello.sh без ./ не работает? Безопасность. Shell ищет команды в $PATH. Если бы текущая директория . была в PATH, злоумышленник мог бы положить ls в /tmp и при cd /tmp && ls ты запустил бы зловреда вместо системного ls.
Поэтому . намеренно не в PATH (на большинстве систем). Чтобы запустить скрипт из текущей директории — указывай явно: ./hello.sh.
Если скрипт в ~/bin или ~/.local/bin и эти каталоги добавлены в PATH (в .bashrc), его можно запускать просто по имени:
$ which my-etl
/home/levo/bin/my-etl
$ my-etl # работает, потому что ~/bin в PATH
Комментарии
#!/usr/bin/env bash
# Это однострочный комментарий
# Многострочные комментарии — несколько строк подряд с #:
# Этот скрипт выполняет ETL-pipeline.
# Запускается каждый день в 06:00 через cron.
# Автор: DE team.
echo "Hello" # комментарий в конце строки тоже работает
Bash не имеет настоящих multi-line comments (как /* ... */ в C). Hack-способ через heredoc:
: '
Это
многострочный
"комментарий" через no-op команду : и here-doc
'
: — встроенная no-op команда (всегда возвращает success). She-doc передаёт ей строки как аргумент, но : их игнорирует. На практике используется редко — лучше просто несколько #-строк.
Переменные окружения и экспорт
#!/usr/bin/env bash
echo "User: $USER"
echo "Home: $HOME"
echo "Path: $PATH"
echo "Сегодня: $(date)"
Доллар-сигн $NAME — подстановка значения переменной. $USER, $HOME, $PATH — стандартные env vars. $(command) — command substitution (запустить команду, вставить её вывод).
Переменные мы детально разберём в уроке 02-variables-and-strings. Пока запомни: $NAME = «вставить значение».
Exit codes: 0 = success
Каждая команда в UNIX возвращает exit code (число 0-255).
0= success.- Любое ненулевое = ошибка.
$ ls /etc
... (список файлов)
$ echo $?
0 # success
$ ls /not-existing
ls: cannot access '/not-existing': No such file or directory
$ echo $?
2 # error (для ls)
$? — переменная с exit code последней команды. Это первое, что проверяют скрипты для error-handling.
Exit code из своего скрипта
#!/usr/bin/env bash
if [[ ! -f /etc/passwd ]]; then
echo "ERROR: /etc/passwd not found" >&2
exit 1
fi
echo "OK"
exit 0 # явное; без exit — return code последней команды
exit N — выход со значимым кодом. По convention:
0— успех.1— generic error.2— misuse of shell builtins (но традиционно: bad arguments).126— command found but not executable (permission denied).127— command not found.128 + N— terminated by signal N (137 = killed by SIGKILL=9, OOM).
В Python мы пишем raise Exception(...). В bash — exit N с осмысленным кодом + понятное сообщение в stderr.
stderr через >&2
echo "ERROR: something bad" >&2
>&2 — перенаправление stdout на FD 2 (stderr). Сообщения об ошибках должны идти в stderr, чтобы их можно было видеть отдельно от обычного вывода в pipelines.
Стандартный паттерн: shebang + set -e + main
Это правильный «production-первый шаг»:
#!/usr/bin/env bash
# orders-etl.sh: запуск ETL для daily orders pipeline
set -euo pipefail
echo "Starting ETL at $(date -Iseconds)"
cd /opt/orders-etl
/opt/orders-etl/venv/bin/python -m orders_etl
echo "ETL completed at $(date -Iseconds)"
exit 0
set -euo pipefail — must-have для каждого production-скрипта:
Каждая буква — отдельная защита от типовых ошибок bash.
Подробнее set -euo pipefail и его подводные камни — в модуле 17-bash-scripting-advanced. Для базового скрипта запомни: всегда первой строкой после shebang.
Comments-стиль: header + sections
Хорошие production-скрипты имеют структуру:
#!/usr/bin/env bash
#
# orders-etl.sh — Daily orders ETL pipeline.
#
# Запускает Python ETL для расчёта орденс-витрин.
# Source: bronze.orders, raw_events
# Sink: silver.orders, gold.orders_daily
#
# Usage:
# /opt/orders-etl/orders-etl.sh [--dry-run]
#
# Schedule:
# systemd timer (см. /etc/systemd/system/orders-etl.timer)
#
# Owner: [email protected]
set -euo pipefail
# --- Config -----------------------------------------------------------
ETL_DIR=/opt/orders-etl
LOG_FILE=/var/log/orders-etl.log
PYTHON=$ETL_DIR/venv/bin/python
# --- Main -------------------------------------------------------------
cd "$ETL_DIR"
echo "[$(date -Iseconds)] ETL started"
"$PYTHON" -m orders_etl
echo "[$(date -Iseconds)] ETL completed"
Хороший header экономит часы следующему DE, который unlocks этот скрипт в 3 часа ночи.
Где живут production-скрипты
Конвенции:
/usr/local/bin/— admin-managed скрипты, system-wide (apt-управляемые в/usr/bin/)./opt/SVC/bin/— application-specific (Airflow в/opt/airflow/bin/).~/bin/или~/.local/bin/— personal user scripts./var/scripts/,/srv/scripts/— иногда для admin scripts.
Главное — постоянное место, добавленное в PATH (через /etc/profile, ~/.bashrc), а не «положу пока в /tmp».
DE-сценарий: deploy-скрипт
Простейший production deploy одной командой:
#!/usr/bin/env bash
#
# deploy.sh — выкатить обновлённый ETL на prod-VM
#
# Usage: ./deploy.sh
set -euo pipefail
REMOTE=airflow-prod
APP_DIR=/opt/orders-etl
LOCAL_DIR=/Users/levo/work/orders-etl
echo "[$(date)] Syncing files to $REMOTE..."
rsync -avz --delete --exclude=.git/ --exclude=venv/ \
"$LOCAL_DIR/" "$REMOTE:$APP_DIR/"
echo "[$(date)] Updating dependencies..."
ssh "$REMOTE" "$APP_DIR/venv/bin/pip install -r $APP_DIR/requirements.txt"
echo "[$(date)] Restarting service..."
ssh "$REMOTE" "sudo systemctl restart orders-etl.timer"
echo "[$(date)] Deploy completed"
Это и есть «production bash». Простой, читаемый, с error-handling через set -e.
Попробуй сам
- Создай свой первый скрипт:
cat > hello.sh <<'EOF' #!/usr/bin/env bash set -euo pipefail echo "Hello, $USER, today is $(date)" EOF chmod +x hello.sh ./hello.sh - Проверь shebang:
head -1 hello.sh - Какой у тебя exit code был:
./hello.sh echo $? - Запусти НЕ-исполняемый файл через bash:
chmod -x hello.sh # снять +x ./hello.sh # упадёт bash hello.sh # работает chmod +x hello.sh # вернуть - Намеренная ошибка для exit code:
cat > bad.sh <<'EOF' #!/usr/bin/env bash exit 42 EOF chmod +x bad.sh ./bad.sh echo $? # должно быть 42
macOS-различия
- На macOS
/bin/bash— Bash 3.2 (древний). Apple замораживает после GPL 3.brew install bash-> bash 5.x в/opt/homebrew/bin/bash(Apple Silicon) или/usr/local/bin/bash(Intel). - Используй
#!/usr/bin/env bash— найдёт правильную версию. - Многие старые скрипты падают на macOS из-за Bash 3.2 (нет ассоциативных массивов до 4.0, нет
mapfile, и т.д.). chmod +x,./script,exit— одинаковы.
Главное
- Shebang: всегда
#!/usr/bin/env bash(переносимо). НЕ#!/bin/bash. - chmod +x делает файл исполняемым. Альтернатива:
bash script.sh. ./script.sh(с./) для запуска из текущей директории. Без./shell ищет в PATH.- Exit code 0 = success, ненулевое = ошибка.
$?— exit code последней команды. exit N— явный выход с кодом. Stderr через>&2.set -euo pipefail— first thing after shebang. Защита от типовых багов:-eexit on error,-uerror on undefined,-o pipefailдля pipes.- Хороший скрипт: shebang -> set -euo pipefail -> header-комментарий -> config -> main.
- Production locations:
/usr/local/bin/,/opt/SVC/bin/,~/.local/bin/. - Comments:
#до конца строки. Многострочные — через несколько#. - Bash для DE: glue-код, обёртки для Python, cleanup, deploy, CI/CD.