Представьте ситуацию: вы написали bash-скрипт, который должен менять поведение в зависимости от контекста выполнения. Например, логировать действия при вызове из другого скрипта или запрещать некоторые операции при запуске через source. Но как программно определить эти состояния? Давайте разберёмся с проверками, которые работают даже в сложных сценариях.
Проверка типа запуска: sourced vs прямой вызов
Ключевой момент – понять, был ли скрипт выполнен через . script.sh (или source script.sh), либо запущен напрямую. Для этого используем сравнение ${BASH_SOURCE[0]} с $0:
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
echo "Скрипт запущен напрямую"
else
echo "Скрипт выполнен через source"
fi
Здесь массив BASH_SOURCE содержит путь к текущему скрипту (в отличие от $0, который в случае source указывает на родительский скрипт). Но есть нюанс: если вы используете функции внутри скрипта, лучше проверять длину массива ${#BASH_SOURCE[@]} – при sourced-вызове она увеличивается.
Совет: Для переносимости между shell (bash, zsh, sh) используйте явное указание интерпретатора в shebang –
#!/usr/bin/env bash
.
Определение контекста выполнения: терминал vs другой скрипт
Самый надёжный способ – передача флага через переменную окружения. Создадим в вызывающем скрипте переменную-маркер:
# caller-script.sh
export CT_CALLER="true"
./target-script.sh
А в целевом скрипте проверяем наличие этой переменной:
if [[ -n "$CT_CALLER" ]]; then
echo "Вызов из другого скрипта"
else
echo "Прямой запуск в терминале"
fi
Но что, если скрипт вызывается через несколько уровней вложенности? Здесь поможет проверка родительского процесса:
PARENT_PID=$(ps -o ppid= -p $ | tr -d ' ')
PARENT_NAME=$(ps -o comm= -p $PARENT_PID)
case $PARENT_NAME in
bash|zsh|sh)
echo "Родитель — оболочка"
;;
*)
echo "Родитель — другой процесс"
;;
esac
Комбинированная проверка всех сценариев
Соберём всё вместе. Пример скрипта check-env.sh, который определяет 4 состояния:
#!/usr/bin/env bash
IS_SOURCED=false
IN_SCRIPT=false
[[ "${BASH_SOURCE[0]}" != "$0" ]] && IS_SOURCED=true
[[ -n "$CT_CALLER" ]] && IN_SCRIPT=true
echo "Состояние:"
echo "- Sourced: $IS_SOURCED"
echo "- Внутри скрипта: $IN_SCRIPT"
if ! $IS_SOURCED && ! $IN_SCRIPT; then
STATUS="Терминал, прямой вызов"
elif $IS_SOURCED && ! $IN_SCRIPT; then
STATUS="Терминал, через source"
elif ! $IS_SOURCED && $IN_SCRIPT; then
STATUS="Другой скрипт, прямой вызов"
else
STATUS="Другой скрипт, через source"
fi
echo "Итог: $STATUS"
Для тестирования создадим драйвер run-tests.sh:
#!/usr/bin/env bash
echo "Тест 1: Прямой запуск в терминале"
./check-env.sh
echo -e "nТест 2: Запуск через source в терминале"
. ./check-env.sh
echo -e "nТест 3: Вызов из скрипта"
export CT_CALLER="runner"
./check-env.sh
echo -e "nТест 4: Source из скрипта"
export CT_CALLER="runner"
. ./check-env.sh
Типичные ошибки и их решение
1. Ложное срабатывание при вызове из cron:
В заданиях cron по умолчанию отсутствует TTY. Добавьте явную проверку:
[[ $(tty) == "not a tty" ]] && echo "Фоновый режим"
2. Конфликт переменных:
Используйте уникальные имена вроде __LIB_SCRIPT_MODE__ вместо общих MODE или STATUS.
3. Вложенный source:
Если скрипт может вызываться через source из другого sourced-контекста, проверяйте глубину стека вызовов:
SOURCE_DEPTH=${#BASH_SOURCE[@]}
(( SOURCE_DEPTH > 2 )) && echo "Вложенный source"
Для сложных сценариев рекомендую добавить логирование. Пример функции:
log_context() {
echo "[DEBUG] PID: $, Parent: $(ps -p $PPID -o comm=), Sourced: ${#BASH_SOURCE[@]}" >&2
}
Эти методы проверены на bash 5.x и zsh 5.8. В старых версиях (bash 3.2 на macOS) возможны расхождения – в этом случае используйте явный вызов актуальной версии через brew install bash. Помните: универсального решения не существует, но комбинация проверок даёт надёжный результат в 99% случаев.