Как определить, запущен ли скрипт в терминале или внутри другого скрипта

Представьте ситуацию: вы написали 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% случаев.

Добавить комментарий

Все поля обязательны к заполнению. Ваш адрес email не будет виден никому.

Новое
Интересное