Вопрос или проблема
[ОБНОВЛЕНИЕ] Спасибо за комментарии и мысли до сих пор. Основываясь на них, я обновил и расширил первоначальный вопрос, добавив больше информации и примеров.
Предположим, на данный момент нет фактического случая использования, единственная цель – выяснить, возможно ли это и если да, то как.
Я хочу запустить скрипт и определить, выполняется ли он напрямую пользователем или вызывается внутри другого скрипта. Я также хочу проверить, является ли он источником или нет.
Это означает, что существует 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 эта задача может быть решена с использованием специфических переменных оболочки и системных команд.
-
Определение способа запуска:
BASH_SOURCE
: Используется для проверки факта подключения (sourcing). ЕслиBASH_SOURCE[0]
равно нулю, это означает, что скрипт запущен, а не сосёрсён.
-
Определение исполнения внутри терминала:
-t
: Проверка того, связано ли стандартное устройство ввода (stdin), вывода (stdout) или стандартное устройство ошибок (stderr) с терминалом.tty
: Устанавливает, есть ли у процесса управляющий терминал.jobs -p
: Устанавливает, выполняется ли процесс на переднем плане.
-
Определение интерактивного режима:
-i
: Принадлежность к интерактивной оболочке, которая имеет специальный флагi
в$-
.
-
Проверка родительского процесса:
PPID
: Переменная, представляющая идентификатор родительского процесса. Использованная командаps
позволяет выяснить тип вызвавшего процесса, что помогает установить, не был ли это другой скрипт или шелл.
Пример
Для выполнения всех перечисленных проверок, в приведённом сценарии check-term
реализованы различного рода проверки с использованием вывода на печать через функцию print_result()
. Основываясь на результатах, выводится соответствующая информация по каждому из тестов. Дополнительно учитывается и сценарий driver
, ответственный за тестирование запуска через выполнение и через sourcing.
Практическое применение
Приведённый пример кода тщательно анализирует каждое из условий, чтобы определить тип контекста, в котором выполняется скрипт. В практическом контексте знание того, каким образом исполняется скрипт, критично для управления потоками исполнения, ресурсами системы и для более изящного управления ошибками и исключениями.
Верный и надёжный метод установления, как выполняется сценарий, позволяет автоматизировать более сложные цепочки задач, зависящие от контекста выполнения. Это может быть применимо в случаях, когда скрипт должен работать иначе в зависимости от того, был ли он вызван из пользовательской сессии или в составе крон-задачи.
Модификации и улучшения
Для более точного опознания условий выполнения, важно тестировать различные условия в разнообразных окружениях, учитывать все возможные состояния проходимости по цепочке вызовов (например, выполнение через ssh). Такие модификации и улучшения могут включать дополнительную проверку переменных окружения, например, для более точного определения, связан ли скрипт с терминалом непосредственно или через псевдотерминал в удалённой сессии.
Заключение
Таким образом, использование доступных средств Bash позволяет надёжно и точно определить условия выполнения скриптов, что расширяет их возможное применение в автоматизации и администрировании систем. При должной настройке, такой подход является мощным инструментом для профессионалов IT, работающих с многозадачными и сложными системами.