Проверьте, выполняется ли скрипт внутри другого скрипта.

Вопрос или проблема

[ОБНОВЛЕНИЕ] Спасибо за комментарии и мысли до сих пор. Основываясь на них, я обновил и расширил первоначальный вопрос, добавив больше информации и примеров.

Предположим, на данный момент нет фактического случая использования, единственная цель – выяснить, возможно ли это и если да, то как.

Я хочу запустить скрипт и определить, выполняется ли он напрямую пользователем или вызывается внутри другого скрипта. Я также хочу проверить, является ли он источником или нет.

Это означает, что существует 4 основных тестовых случая:

  • запуск в терминале, не является источником
  • запуск в терминале, является источником
  • запуск в скрипте (не в терминале), не является источником
  • запуск в скрипте (не в терминале), является источником

Мой очень ржавый мозг, Google и ChatGPT дали мне список проверок, чтобы протестировать, выполняется ли в терминале, но ни одна из них не дает правильных результатов для всех 4 вызовов.

Тесты находятся в check-term, и ниже представлен вызывающий скрипт driver.

Если я выполняю 4 тестовых случая в порядке, указанном выше, для правильности данного тестового номера (предположим, номер теста n), я хочу, чтобы вывод был таким:

n: в терминале:     не является источником: ...
n: в терминале:     является источником: ...
n: не в терминале: не является источником: ...
n: не в терминале: является источником: ...

Тест на то, является ли источником или нет, корректен для всех 4 вызовов.

Скрипт check-term:

#!/usr/bin/env bash

IS_TERMINAL=true
NOT_TERMINAL=false
TEST_NUM=0
SOURCED_TEXT="не определено"

print_result() {
  TEST_NUM=$((TEST_NUM + 1))
  if [ "$1" = "$IS_TERMINAL" ]; then
    printf "%2d: %-16s %-12s %s\n" "$TEST_NUM" "в терминале:" "$SOURCED_TEXT:" "$2"
  else
    printf "%2d: %-16s %-12s %s\n" "$TEST_NUM" "не в терминале:" "$SOURCED_TEXT:" "$2"
  fi
}

# Сначала проверьте, является ли скрипт источником или нет.
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
  SOURCED_TEXT="не является источником"
else
  SOURCED_TEXT="является источником"
fi

# Тесты запускаются индивидуально в расширенных if/else для ясности.
# - Условие теста описано текстом результата.
if [ -t 0 ]; then
    print_result "$IS_TERMINAL" "stdin является терминалом"
else
    print_result "$NOT_TERMINAL" "stdin не является терминалом"
fi

if [ -t 1 ]; then
    print_result "$IS_TERMINAL" "stdout является терминалом"
else
    print_result "$NOT_TERMINAL" "stdout не является терминалом"
fi

if [ -t 2 ]; then
    print_result "$IS_TERMINAL" "stderr является терминалом"
else
    print_result "$NOT_TERMINAL" "stderr не является терминалом"
fi

if [[ "$-" == *i* ]]; then
    print_result "$IS_TERMINAL" "интерактивная оболочка"
else
    print_result "$NOT_TERMINAL" "неинтерактивная оболочка"
fi

if [ -n "$(tty)" ]; then
    print_result "$IS_TERMINAL" "имеет управляющий терминал"
else
    print_result "$NOT_TERMINAL" "не имеет управляющего терминала"
fi

if [ -p /dev/stdin ]; then
    print_result "$NOT_TERMINAL" "выполняется в конвейере"
else
    print_result "$IS_TERMINAL" "не выполняется в конвейере"
fi

if [ -z "$(jobs -p)" ]; then
    print_result "$IS_TERMINAL" "выполняется в переднем плане"
else
    print_result "$NOT_TERMINAL" "выполняется в фоновом режиме"
fi

# Получить ID процесса, который запустил скрипт.
PPID_VALUE=$(ps -o ppid= -p $$ | awk 'NR==2 {print $1}')

# Если ID не может быть найдено, дайте ему значение по умолчанию.
[ -z "$PPID_VALUE" ] && PPID_VALUE=1

# Проверить, прикреплен ли или нет.
if [ "$PPID_VALUE" -ne 1 ]; then
    print_result "$IS_TERMINAL" "прикреплен к родительскому процессу"
else
    print_result "$NOT_TERMINAL" "отключен от родительского процесса"
fi

# Проверить, является ли родительский процесс оболочкой
PARENT_CMD=$(ps -o args= -p "$PPID_VALUE" 2>/dev/null)
if echo "$PARENT_CMD" | grep -qE '/(bash|zsh|sh)(\s|$)'; then
    print_result "$IS_TERMINAL" "родительский процесс является оболочкой"
else
    print_result "$NOT_TERMINAL" "родительский процесс не является оболочкой"
fi

Скрипт Driver:

#!/usr/bin/env bash

./check-term
echo ""
. ./check-term

Ответ или решение

Вопрос, связанный с определением, выполняется ли сценарий непосредственно пользователем или вызывается внутри другого сценария, а также является ли он подключённым через source, представляет определенный интерес в сфере скриптинга и автоматизации процессов. Здесь рассматриваются четыре основные тестовые ситуации: запуск из терминала, без подключения через source; запуск из терминала с подключением через source; запуск внутри другого скрипта без использования терминала и без подключения через source; и, наконец, запуск внутри другого скрипта с подключением через source. Рассмотрим, как эти состояния могут быть выявлены.

Теория

Основной задачей является различение прямого запуска скрипта пользователем и исполнения его через другой скрипт. Одновременно с этим, необходимо понять, был ли скрипт "подключён" или "сосёрсён" (sourced). В Bash эта задача может быть решена с использованием специфических переменных оболочки и системных команд.

  1. Определение способа запуска:

    • BASH_SOURCE: Используется для проверки факта подключения (sourcing). Если BASH_SOURCE[0] равно нулю, это означает, что скрипт запущен, а не сосёрсён.
  2. Определение исполнения внутри терминала:

    • -t: Проверка того, связано ли стандартное устройство ввода (stdin), вывода (stdout) или стандартное устройство ошибок (stderr) с терминалом.
    • tty: Устанавливает, есть ли у процесса управляющий терминал.
    • jobs -p: Устанавливает, выполняется ли процесс на переднем плане.
  3. Определение интерактивного режима:

    • -i: Принадлежность к интерактивной оболочке, которая имеет специальный флаг i в $-.
  4. Проверка родительского процесса:

    • PPID: Переменная, представляющая идентификатор родительского процесса. Использованная команда ps позволяет выяснить тип вызвавшего процесса, что помогает установить, не был ли это другой скрипт или шелл.

Пример

Для выполнения всех перечисленных проверок, в приведённом сценарии check-term реализованы различного рода проверки с использованием вывода на печать через функцию print_result(). Основываясь на результатах, выводится соответствующая информация по каждому из тестов. Дополнительно учитывается и сценарий driver, ответственный за тестирование запуска через выполнение и через sourcing.

Практическое применение

Приведённый пример кода тщательно анализирует каждое из условий, чтобы определить тип контекста, в котором выполняется скрипт. В практическом контексте знание того, каким образом исполняется скрипт, критично для управления потоками исполнения, ресурсами системы и для более изящного управления ошибками и исключениями.

Верный и надёжный метод установления, как выполняется сценарий, позволяет автоматизировать более сложные цепочки задач, зависящие от контекста выполнения. Это может быть применимо в случаях, когда скрипт должен работать иначе в зависимости от того, был ли он вызван из пользовательской сессии или в составе крон-задачи.

Модификации и улучшения

Для более точного опознания условий выполнения, важно тестировать различные условия в разнообразных окружениях, учитывать все возможные состояния проходимости по цепочке вызовов (например, выполнение через ssh). Такие модификации и улучшения могут включать дополнительную проверку переменных окружения, например, для более точного определения, связан ли скрипт с терминалом непосредственно или через псевдотерминал в удалённой сессии.

Заключение

Таким образом, использование доступных средств Bash позволяет надёжно и точно определить условия выполнения скриптов, что расширяет их возможное применение в автоматизации и администрировании систем. При должной настройке, такой подход является мощным инструментом для профессионалов IT, работающих с многозадачными и сложными системами.

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

Капча загружается...