Вопрос или проблема
Либо то, о чем я здесь спрашиваю, является крайне нетрадиционным/нетипичным/рискованным, либо мои навыки поиска в Google просто не на высоте…
В сценарии оболочки bash
есть ли какой-либо простой способ определить, является ли он источником другого сценария оболочки или запускается самостоятельно? Другими словами, возможно ли различить следующие два поведения?
# из другого сценария оболочки
source myScript.sh
# из командной строки или другого сценария оболочки
./myScript.sh
Я думаю о создании сценария оболочки, похожего на утилиты, содержащего функции bash
, которые могут быть доступны при их использовании как источник. Когда этот сценарий запускается сам по себе, я хочу, чтобы он выполнял определенные операции, также на основе определенных функций. Существует ли какой-нибудь переменный показатель окружения, который этот сценарий оболочки может распознать, например:
some_function() {
# ...
}
if [ -z "$IS_SOURCED" ]; then
some_function;
fi
Предпочтительно, я ищу решение, которое не требует от вызывающего сценария установки каких-либо флаговых переменных.
изменено: Я знаю разницу между использованием сценария как источника и его выполнением, я пытаюсь выяснить здесь, возможно ли определить различие в сценарии, который используется (в обоих случаях).
Да – переменная $0 дает имя сценария, как он был запущен:
$ cat example.sh
#!/bin/bash
script_name=$( basename ${0#-} ) #- необходимо, если используется источник, без пути
this_script=$( basename ${BASH_SOURCE} )
if [[ ${script_name} = ${this_script} ]] ; then
echo "запущен напрямую"
else
echo "исходен из ${script_name}"
fi
$ cat example2.sh
#!/bin/bash
. ./example.sh
Что выполняется следующим образом:
$ ./example.sh
запущен напрямую
$ ./example2.sh
example.sh исходен из example2.sh
Это не учитывает использование из интерактивной оболочки, но, надеюсь, вы поймете идею.
Обновлено для включения BASH_SOURCE – спасибо h.j.k
Комбинирование ответа @DarkHeart’s с переменной окружения BASH_SOURCE
кажется даёт результат:
$ head example*.sh
==> example2.sh <==
#!/bin/bash
. ./example.sh
==> example.sh <==
#!/bin/bash
if [ "$(basename $0)" = "$(basename $BASH_SOURCE)" ]; then
echo "запущен напрямую"
else
echo "исходен из $0"
fi
$ ./example2.sh
исходен из ./example2.sh
$ ./example.sh
запущен напрямую
изменено Кажется, есть более простое решение, если просто посчитать количество элементов в массиве BASH_SOURCE
:
if [ ${#BASH_SOURCE[@]} -eq 1 ]; then echo "запущен напрямую"; else echo "исходен из $0"; fi
Я просто создал такой же скрипт библиотеки, который работает как BusyBox. В нем я использую следующую функцию для проверки, является ли он источником…
function isSourced () {
[[ "${FUNCNAME[1]}" == "source" ]] && return 0
return 1
}
Массив FUNCNAME, поддерживаемый Bash, представляет собой, по сути, стек вызовов функций. $FUNCNAME
(или ${FUNCNAME[0]}
) – имя выполняемой в данный момент функции. ${FUNCNAME[1]}
– имя функции, которая её вызвала, и так далее.
Самый верхний элемент имеет специальное значение для самого сценария. Он будет содержать…
- слово “source”, если сценарий является источником
- слово “main”, если сценарий выполняется И проверка проводится внутри функции
- “”(null) если сценарий выполняется И проверка проводится вне какой-либо функции, т.е. на уровне самого сценария.
Указанная выше функция действительно работает только при вызове на уровне сценария (что все, что мне нужно). Она не сработает, если её вызвать из другой функции, потому что номер элемента массива будет неправильным. Чтобы она работала везде, требуется найти вершину стека и провести тестирование этого значения, что более сложно.
Если это необходимо, можно получить номер элемента массива “вершины стека” с помощью…
local _top_of_stack=$(( ${#FUNCNAME[@]} - 1 ))
${#FUNCNAME[@]}
– это количество элементов в массиве. Как массив с нумерацией, начинающейся с нуля, вычитаем 1, чтобы получить последний элемент#.
Эти три функции используются вместе для создания трассировки стека функций, подобной Python, и они могут дать вам лучшее представление о том, как это работает…
function inspFnStack () {
local T+=" "
local _at=
local _text="\n"
local _top=$(inspFnStackTop)
local _fn=${FUNCNAME[1]}; [[ $_fn =~ source|main ]] || _fn+="()"
local i=_top; ((--i))
#
_text+="$i элемент стек вызовов функций для $_fn ...\n"
_text+="| L BASH_SOURCE{BASH_LINENO вызвано из}.FUNCNAME \n"
_text+="| ---------------------------------------------------\n"
while (( $i > 0 ))
do
_text+="| $i ${T}$(inspFnStackItem $i)\n"
T+=" "
((--i))
done
#
printf "$_text\n"
#
return 0
}
function inspFnStackItem () {
local _i=$1
local _fn=${FUNCNAME[$_i]}; [[ $_fn =~ source|main ]] || _fn+="()"
local _at="${BASH_LINENO[$_i-1]}"; [[ $_at == 1 ]] && _at="trap"
local _item="${BASH_SOURCE[$_i]}{${_at}}.$_fn"
#
printf "%s" "$_item"
return 0
}
function inspFnStackTop () {
# верхний элемент стека на 1 меньше длины массива стека FUNCNAME
printf "%d\n" $(( ${#FUNCNAME[@]} - 1 ))
#
return 0
}
Обратите внимание, что FUNCNAME, BASH_SOURCE и BASH_LINENO – это 3 массива, поддерживаемых bash, как если бы они были одним трехмерным массивом.
Один из способов, который также работает при выполнении из интерактивной оболочки:
if [ $BASH_LINENO -ne 0 ]; then
some_function;
fi
Переменная BASH_LINENO
также является массивом со всеми линиями, на которых была выполнена вызывающая функция. Она будет равна нулю, если сценарий вызывается напрямую, или целое число, соответствующее номеру строки.
Просто хочу добавить, что подсчет по массиву может быть ненадежным, и, вероятно, не следует предполагать, что использовалась команда source
, так как также очень распространено использование точки (.
) (и она предшествует ключевому слову source
).
Например, для сценария sourced.sh
, содержащего только echo $0
:
$ . sourced.sh
bash
$ source sourced.sh
bash
$ chmod +x sourced.sh
$ ./sourced.sh
./sourced.sh
$ cat ./sourced.sh
echo $0
Предложенные решения с использованием сравнения работают лучше.
Вы можете проверить, был ли файл использован с помощью этой функции
script::is_sourced() {
for func in "${FUNCNAME[@]}"; do
if [[ "$func" == "source" ]]; then
return 0 # Исходен
fi
done
return 1 # Не исходен
}
Объяснение:
В массиве FUNCNAME
:
- последний элемент – это
main
- и если использован как источник, одной из следующих функций будет
source
(т.е. изsource filename
)
Вы можете увидеть это, напечатав (например, с помощью этой функции stack::print)
- первый вызов
main
– элемент5
, - вызов
source
– элемент4
и происходит на строке183
0: /code/bash-lib/lib/bashlib-stack.sh#stack::print:0
1: /code/bash-lib/lib/bashlib-script.sh#script::is_sourced:10
2: /code/bash-lib/lib/bashlib-script.sh#script::get_actual_script:21
3: /code/kubee/bin/kubee-cluster#synopsis:126
4: /admin/code/kubee/bin/kubee-cluster#source:183
5: /admin/code/kubee/bin/kubee#main:95
Формат:
позиция элемента FUNCNAME
путь скрипта
#имя_функции
:номер строки
Ответ или решение
Понимание разницы между выполнением и подключением скрипта в Bash является важным аспектом для IT-специалистов, особенно когда нужно создавать утилитарные скрипты, которые могут работать как самостоятельные программы или расширять возможности других скриптов.
Теория
При работе с Bash или другими Unix-оболочками можно встретиться с двумя основными способами использования скриптов:
-
Запуск скрипта: скрипт выполняется как отдельное приложение. При этом создается новый процесс. Запуск скрипта осуществляется, например, с использованием команды
./myScript.sh
. -
Подключение (sourcing) скрипта: используется команда
source myScript.sh
(или ее аналог.
), что позволяет выполнять команды скрипта в текущем процессе оболочки. Это полезно для загрузки функций и переменных, которые будут доступны в текущем сеансе оболочки.
Пример
Для проверки, является ли текущий скрипт подключенным или выполняемым, можно использовать переменные окружения, которые предоставляет Bash. Наиболее распространенные переменные для этой цели – $0
, $BASH_SOURCE
, FUNCNAME[]
и BASH_LINENO[]
.
В примере ниже описывается, как можно определить, запущен ли скрипт напрямую или подключен:
#!/bin/bash
if [ "$(basename "$0")" = "$(basename "${BASH_SOURCE[0]}")" ]; then
echo "Сценарий запущен напрямую."
else
echo "Сценарий подключен из $(basename "$0")."
fi
Разбор
"$0"
: Содержит имя команды или скрипта, когда выполняется как приложение."$BASH_SOURCE[0]"
: Содержит путь к файлу скрипта. Будет совпадать с$0
, если скрипт запущен, но будет различаться, если скрипт подключен.
Применение
Эта техника может быть особенно полезна в ситуациях, когда нужно различать две стратегии выполнения скрипта. Например, ваш скрипт может содержать набор функций, которые предлагается использовать в других скриптах. При этом, если скрипт запускается напрямую, он может выполнять основную логику приложения.
Дополнительно, можно использовать массив FUNCNAME[]
для определения источника вызова. Если вам требуется больше гибкости, можно написать функцию, проверяющую, является ли скрипт подключенным:
function isSourced () {
for func in "${FUNCNAME[@]}"; do
if [[ "$func" == "source" ]]; then
return 0 # Скрипт подключен
fi
done
return 1 # Скрипт не подключен
}
if isSourced; then
echo "Сценарий подключен."
else
echo "Сценарий запущен напрямую."
fi
Заключение
Понимание того, как различать запущенные и подключенные скрипты в Bash, необходимы для эффективного управления сценариями в разных условиях. Это позволяет создавать более универсальные, модульные и переиспользуемые решения, что особенно важно для поддержки больших систем и приложений в профессиональной среде IT.
Проблема различия между запущенными и подключенными скриптами решается на уровне понимания внутреннего устройства Bash и его переменных среды. Развитие таких навыков позволяет IT-специалистам более эффективно использовать инструменты операционной системы и улучшать процессы автоматизации.