Как определить, есть ли script
У меня есть скрипт, в котором я не хочу, чтобы он вызывал exit
если он получен.
Я подумал о проверке $0 == bash
но это имеет проблемы, если скрипт получен из другого скрипта, или если пользователь получает его из другой оболочки, такой как ksh
.
Есть ли надежный способ определить, что сценарий был получен?
Ответы
Ответ 1
Это похоже на переносимость между Bash и Korn:
[[ $_ != $0 ]] && echo "Script is being sourced" || echo "Script is a subshell"
Строка, аналогичная этой, или присваивание, подобное pathname="$_"
(с последующим тестом и действием), должно находиться в первой строке скрипта или в строке после shebang (которая, если она используется, должна быть для ksh в для того, чтобы он работал в большинстве случаев).
Ответ 2
Если ваша версия Bash знает о переменной массива BASH_SOURCE, попробуйте что-то вроде:
# man bash | less -p BASH_SOURCE
#[[ ${BASH_VERSINFO[0]} -le 2 ]] && echo 'No BASH_SOURCE array variable' && exit 1
[[ "${BASH_SOURCE[0]}" != "${0}" ]] && echo "script ${BASH_SOURCE[0]} is being sourced ..."
Ответ 3
Надежные решения для bash
, ksh
, zsh
, в том числе с перекрестной оболочкой, а также достаточно надежное POSIX-совместимое решение:
-
Приведенные номера версий - это те, на которых была проверена функциональность - вероятно, эти решения работают и на гораздо более ранних версиях - обратная связь приветствуется.
-
Используя только функции POSIX (например, в dash
, который действует как /bin/sh
в Ubuntu), нет надежного способа определить, идет ли сценарий из источника - наилучшее приближение см. Ниже.
Последующие строки - пояснение ниже; версия с несколькими оболочками сложна, но она должна работать надежно:
-
bash (проверено в 3.57 и 4.4.19)
(return 0 2>/dev/null) && sourced=1 || sourced=0
-
ksh (проверено 93u+)
[[ $(cd "$(dirname -- "$0")" &&
printf '%s' "${PWD%/}/")$(basename -- "$0") != "${.sh.file}" ]] &&
sourced=1 || sourced=0
-
zsh (проверено на 5.0.5) - обязательно вызовите это вне функции
[[ $ZSH_EVAL_CONTEXT =~ :file$ ]] && sourced=1 || sourced=0
-
поперечная оболочка (bash, ksh, zsh)
([[ -n $ZSH_EVAL_CONTEXT && $ZSH_EVAL_CONTEXT =~ :file$ ]] ||
[[ -n $KSH_VERSION && $(cd "$(dirname -- "$0")" &&
printf '%s' "${PWD%/}/")$(basename -- "$0") != "${.sh.file}" ]] ||
[[ -n $BASH_VERSION ]] && (return 0 2>/dev/null)) && sourced=1 || sourced=0
-
POSIX-совместимый; не однострочный (однотрубный) по техническим причинам и не полностью устойчивый (см. внизу):
sourced=0
if [ -n "$ZSH_EVAL_CONTEXT" ]; then
case $ZSH_EVAL_CONTEXT in *:file) sourced=1;; esac
elif [ -n "$KSH_VERSION" ]; then
[ "$(cd $(dirname -- $0) && pwd -P)/$(basename -- $0)" != "$(cd $(dirname -- ${.sh.file}) && pwd -P)/$(basename -- ${.sh.file})" ] && sourced=1
elif [ -n "$BASH_VERSION" ]; then
(return 0 2>/dev/null) && sourced=1
else # All other shells: examine $0 for known shell binary filenames
# Detects 'sh' and 'dash'; add additional shell filenames as needed.
case ${0##*/} in sh|dash) sourced=1;; esac
fi
Объяснение:
удар
(return 0 2>/dev/null) && sourced=1 || sourced=0
Примечание. Этот метод был адаптирован из ответа пользователя user5754163, поскольку он оказался более надежным, чем исходное решение, [[ $0 != "$BASH_SOURCE" ]] && sourced=1 || sourced=0
[[ $0 != "$BASH_SOURCE" ]] && sourced=1 || sourced=0
[1]
-
Bash позволяет return
операторы только из функций и, в области видимости верхнего уровня скрипта, только если исходный код скрипта.
- Если
return
используется в области верхнего уровня сценария без источников, выдается сообщение об ошибке, и код выхода устанавливается равным 1
.
-
(return 0 2>/dev/null)
выполняет return
в подоболочке и подавляет сообщение об ошибке; после этого код завершения указывает, был ли скрипт получен (0
) или нет (1
), который используется с &&
и ||
операторы, чтобы установить sourced
переменную соответственно.
- Использование подоболочки необходимо, потому что выполнение
return
в области верхнего уровня сценария с исходным кодом приведет к его завершению. - Наконечник шляпы @Haozhun, который сделал команду более устойчивой, явно используя
0
в качестве операнда return
;он отмечает: в bash help of return [N]
: "Если N опущено, возвращается статус последней команды".В результате более ранняя версия [которая использовала только return
, без операнда] дает неверный результат, если последняя команда в пользовательской оболочке имеет ненулевое возвращаемое значение.
КШ
[[ \
$(cd "$(dirname -- "$0")" && printf '%s' "${PWD%/}/")$(basename -- "$0") != \
"${.sh.file}" \
]] &&
sourced=1 || sourced=0
Специальная переменная ${.sh.file}
чем-то аналогична $BASH_SOURCE
; обратите внимание, что ${.sh.file}
вызывает синтаксическую ошибку в bash, zsh и dash, поэтому обязательно выполняйте ее условно в сценариях с несколькими оболочками.
В отличие от bash, $0
и ${.sh.file}
НЕ гарантированно идентичны в случае без источников, поскольку $0
может быть относительным путем, в то время как ${.sh.file}
всегда является полным путем, поэтому $0
должен быть преобразован в полный путь перед сравнением.
ЗШ
[[ $ZSH_EVAL_CONTEXT =~ :file$ ]] && sourced=1 || sourced=0
$ZSH_EVAL_CONTEXT
содержит информацию о контексте оценки - вызывайте ее вне функции. Внутри исходного скрипта [область верхнего уровня] $ZSH_EVAL_CONTEXT
оканчивается на :file
.
Предупреждение: внутри подстановки команд zsh добавляет :cmdsubst
, поэтому проверьте $ZSH_EVAL_CONTEXT
наличие :file:cmdsubst$
там.
Использование только функций POSIX
Если вы хотите сделать определенные предположения, вы можете сделать разумное, но не надежное предположение о том, что ваш сценарий получен, основываясь на знании двоичных имен файлов оболочек, которые могут выполнять ваш сценарий.
В частности, это означает, что этот подход не работает, если ваш сценарий получен из другого сценария.
Раздел "Как обрабатывать вызовы из источника" в этом моем ответе обсуждает крайние случаи, которые не могут быть обработаны только с помощью функций POSIX.
Это основано на стандартном поведении $0
, которое, например, не показывает zsh
.
Таким образом, самый безопасный подход состоит в том, чтобы объединить надежные, специфичные для оболочки методы, описанные выше, с запасным решением для всех оставшихся оболочек.
Наконечник шляпы Стефану Десне и его ответу за вдохновение (преобразование моего выражения оператора через оболочку в выражение sh
-compatible if
и добавление обработчика для других оболочек).
sourced=0
if [ -n "$ZSH_EVAL_CONTEXT" ]; then
case $ZSH_EVAL_CONTEXT in *:file) sourced=1;; esac
elif [ -n "$KSH_VERSION" ]; then
[ "$(cd $(dirname -- $0) && pwd -P)/$(basename -- $0)" != "$(cd $(dirname -- ${.sh.file}) && pwd -P)/$(basename -- ${.sh.file})" ] && sourced=1
elif [ -n "$BASH_VERSION" ]; then
(return 0 2>/dev/null) && sourced=1
else # All other shells: examine $0 for known shell binary filenames
# Detects 'sh' and 'dash'; add additional shell filenames as needed.
case ${0##*/} in sh|dash) sourced=1;; esac
fi
[1] user1902689 обнаружил, что [[ $0 != "$BASH_SOURCE" ]]
выдает ложное срабатывание при выполнении скрипта, расположенного в $PATH
путем передачи его простого имени файла в двоичный файл bash
;например, bash my-script
, потому что $0
- это просто my-script
, а $BASH_SOURCE
- полный путь.Хотя вы обычно не используете эту технику для вызова сценариев в $PATH
- вы просто вызываете их напрямую (my-script
) - это полезно в сочетании с -x
для отладки.
Ответ 4
После прочтения ответа @DennisWilliamson, есть некоторые проблемы, см. ниже:
Как этот вопрос стоит ksh и bash, в этом ответе есть еще одна часть, касающаяся ksh... см. ниже.
Простой bash way
[ "$0" = "$BASH_SOURCE" ]
Попробуем (на лету, потому что bash может; -):
source <(echo $'#!/bin/bash
[ "$0" = "$BASH_SOURCE" ] && v=own || v=sourced;
echo "process $$ is $v ($0, $BASH_SOURCE)" ')
process 29301 is sourced (bash, /dev/fd/63)
bash <(echo $'#!/bin/bash
[ "$0" = "$BASH_SOURCE" ] && v=own || v=sourced;
echo "process $$ is $v ($0, $BASH_SOURCE)" ')
process 16229 is own (/dev/fd/63, /dev/fd/63)
Я использую source
вместо .
для чтения (поскольку .
является псевдонимом source
):
. <(echo $'#!/bin/bash
[ "$0" = "$BASH_SOURCE" ] && v=own || v=sourced;
echo "process $$ is $v ($0, $BASH_SOURCE)" ')
process 29301 is sourced (bash, /dev/fd/63)
Обратите внимание, что номер процесса не изменяется при сохранении процесса:
echo $$
29301
Почему бы не использовать сравнение $_ == $0
Для обеспечения многих случаев я начинаю писать true script:
#!/bin/bash
# As $_ could be used only once, uncomment one of two following lines
#printf '_="%s", 0="%s" and BASH_SOURCE="%s"\n' "$_" "$0" "$BASH_SOURCE"
[[ "$_" != "$0" ]] && DW_PURPOSE=sourced || DW_PURPOSE=subshell
[ "$0" = "$BASH_SOURCE" ] && BASH_KIND_ENV=own || BASH_KIND_ENV=sourced;
echo "proc: $$[ppid:$PPID] is $BASH_KIND_ENV (DW purpose: $DW_PURPOSE)"
Скопируйте его в файл с именем testscript
:
cat >testscript
chmod +x testscript
Теперь мы можем проверить:
./testscript
proc: 25758[ppid:24890] is own (DW purpose: subshell)
Это нормально.
. ./testscript
proc: 24890[ppid:24885] is sourced (DW purpose: sourced)
source ./testscript
proc: 24890[ppid:24885] is sourced (DW purpose: sourced)
Это нормально.
Но для тестирования script перед добавлением флага -x
:
bash ./testscript
proc: 25776[ppid:24890] is own (DW purpose: sourced)
Или использовать предопределенные переменные:
env PATH=/tmp/bintemp:$PATH ./testscript
proc: 25948[ppid:24890] is own (DW purpose: sourced)
env SOMETHING=PREDEFINED ./testscript
proc: 25972[ppid:24890] is own (DW purpose: sourced)
Это больше не будет работать.
Перемещение комментария с 5-й строки на 6-й даст более читаемый ответ:
./testscript
_="./testscript", 0="./testscript" and BASH_SOURCE="./testscript"
proc: 26256[ppid:24890] is own
. testscript
_="_filedir", 0="bash" and BASH_SOURCE="testscript"
proc: 24890[ppid:24885] is sourced
source testscript
_="_filedir", 0="bash" and BASH_SOURCE="testscript"
proc: 24890[ppid:24885] is sourced
bash testscript
_="/bin/bash", 0="testscript" and BASH_SOURCE="testscript"
proc: 26317[ppid:24890] is own
env FILE=/dev/null ./testscript
_="/usr/bin/env", 0="./testscript" and BASH_SOURCE="./testscript"
proc: 26336[ppid:24890] is own
Harder: ksh сейчас...
Поскольку я не использую ksh много, после некоторых прочитанных на странице man, есть мои попытки:
#!/bin/ksh
set >/tmp/ksh-$$.log
Скопируйте это в testfile.ksh
:
cat >testfile.ksh
chmod +x testfile.ksh
Запустите его два раза:
./testfile.ksh
. ./testfile.ksh
ls -l /tmp/ksh-*.log
-rw-r--r-- 1 user user 2183 avr 11 13:48 /tmp/ksh-9725.log
-rw-r--r-- 1 user user 2140 avr 11 13:48 /tmp/ksh-9781.log
echo $$
9725
и посмотрите:
diff /tmp/ksh-{9725,9781}.log | grep ^\> # OWN SUBSHELL:
> HISTCMD=0
> PPID=9725
> RANDOM=1626
> SECONDS=0.001
> lineno=0
> SHLVL=3
diff /tmp/ksh-{9725,9781}.log | grep ^\< # SOURCED:
< COLUMNS=152
< HISTCMD=117
< LINES=47
< PPID=9163
< PS1='$ '
< RANDOM=29667
< SECONDS=23.652
< level=1
< lineno=1
< SHLVL=2
Существует некоторая переменная, включенная в исходный код, но ничего действительно не связано...
Вы даже можете проверить, что $SECONDS
близок к 0.000
, но это гарантирует только случаи с ручной копией...
Вы даже можете попробовать проверить, что такое родитель:
Поместите это в свой testfile.ksh
:
ps $PPID
чем:
./testfile.ksh
PID TTY STAT TIME COMMAND
32320 pts/4 Ss 0:00 -ksh
. ./testfile.ksh
PID TTY STAT TIME COMMAND
32319 ? S 0:00 sshd: [email protected]/4
или ps ho cmd $PPID
, но это работает только для одного уровня подсекций...
Извините, я не смог найти надежный способ сделать это под ksh.
Ответ 5
Ответ BASH_SOURCE[]
(bash -3.0 и более поздний) кажется простым, хотя BASH_SOURCE[]
не документирован для работы вне тела функции (в настоящее время он работает, в несогласии с man-страницей).
Самый надежный способ, предложенный Wirawan Purwanto, - проверить FUNCNAME[1]
внутри функции:
function mycheck() { declare -p FUNCNAME; }
mycheck
Тогда:
$ bash sourcetest.sh
declare -a FUNCNAME='([0]="mycheck" [1]="main")'
$ . sourcetest.sh
declare -a FUNCNAME='([0]="mycheck" [1]="source")'
Это эквивалентно проверке вывода caller
, значения main
и source
различают контекст вызывающего абонента. Использование FUNCNAME[]
экономит ваш захват и разбор caller
. Однако вам нужно знать или рассчитать глубину вашего локального звонка. Случаи, подобные script, полученные из другой функции или script, заставят массив (стек) глубже. (FUNCNAME
является специальной переменной массива bash, она должна иметь непрерывные индексы, соответствующие стеку вызовов, если она никогда не unset
.)
function issourced() {
[[ ${FUNCNAME[@]: -1} == "source" ]]
}
(В bash -4.2 и позже вы можете использовать более простую форму ${FUNCNAME[-1]}
вместо последнего элемента в массиве. Улучшено и упрощено благодаря комментарию Денниса Уильямсона ниже.)
Однако ваша проблема: "У меня есть script, где я не хочу, чтобы он вызывал" exit ", если он был источником". Общая идиома bash
для этой ситуации:
return 2>/dev/null || exit
Если script будет отправлен, то return
завершит исходный код script и вернется к вызывающему.
Если выполняется script, то return
вернет ошибку (перенаправляется), а exit
завершит script как обычно. Оба return
и exit
могут принимать код выхода, если это необходимо.
К сожалению, это не работает в ksh
(по крайней мере, не в производной версии AT & T, которую я здесь), он рассматривает return
как эквивалент exit
, если он вызван вне функции или с точки зрения источника script.
Обновлено. Что вы можете сделать в современных версиях ksh
, это проверить специальную переменную .sh.level
, которая установлена на глубину вызова функции. Для вызванного script это изначально будет отменено, для точечного источника script он будет установлен в 1.
function issourced {
[[ ${.sh.level} -eq 2 ]]
}
issourced && echo this script is sourced
Это не так надежно, как версия bash, вы должны вызывать issourced()
в файле, который вы тестируете, на верхнем уровне или на известной глубине функции.
(Вас также может заинтересовать этот код в github, который использует функцию дисциплины ksh
и некоторую отладочную ловушку для подражания bash FUNCNAME
.)
Канонический ответ здесь: http://mywiki.wooledge.org/BashFAQ/109 также предлагает $-
как еще один индикатор (хотя и несовершенный) состояния оболочки.
Примечания:
- можно создать bash функции с именем "main" и "source" (переопределить встроенный файл), эти имена могут отображаться в
FUNCNAME[]
, но как поскольку проверяется только последний элемент в этом массиве, нет никакой двусмысленности.
- У меня нет хорошего ответа для
pdksh
. Самое близкое, что я могу найти, относится только к pdksh
, где каждый источник script открывает новый файловый дескриптор (начиная с 10 для оригинала script). Почти наверняка не то, на что вы хотите положиться...
Ответ 6
Примечание редактора: это решение для ответов работает надежно, но это bash
-only.Это может быть упрощено до
(return 2>/dev/null)
.
TL; DR
Попробуйте выполнить инструкцию return
. Если сценарий не получен, это вызовет ошибку. Вы можете поймать эту ошибку и действовать так, как вам нужно.
Поместите это в файл и назовите его, скажем, test.sh:
#!/usr/bin/env sh
# Try to execute a 'return' statement,
# but do it in a sub-shell and catch the results.
# If this script isn't sourced, that will raise an error.
$(return >/dev/null 2>&1)
# What exit code did that give?
if [ "$?" -eq "0" ]
then
echo "This script is sourced."
else
echo "This script is not sourced."
fi
Выполните это непосредственно:
shell-prompt> sh test.sh
output: This script is not sourced.
Источник это:
shell-prompt> source test.sh
output: This script is sourced.
Для меня это работает в Zsh и Bash.
объяснение
Оператор return
вызовет ошибку, если вы попытаетесь выполнить ее вне функции или если скрипт не получен. Попробуйте это из командной строки:
shell-prompt> return
output: ...can only 'return' from a function or sourced script
Вам не нужно видеть это сообщение об ошибке, поэтому вы можете перенаправить вывод в dev/null:
shell-prompt> return >/dev/null 2>&1
Теперь проверьте код выхода. 0 означает, что все в порядке (ошибок не было), 1 означает, что произошла ошибка:
shell-prompt> echo $?
output: 1
Вы также хотите выполнить инструкцию return
внутри вложенной оболочки. Когда оператор return
запускает его. , , Что ж. , , возвращается. Если вы выполните его в под-оболочке, он вернется из этой под-оболочки, а не из вашего скрипта. Чтобы выполнить в под-оболочке, оберните его в $(...)
:
shell-prompt> $(return >/dev/null 2>$1)
Теперь вы можете увидеть код завершения вложенной оболочки, который должен быть равен 1, поскольку внутри вложенной оболочки возникла ошибка:
shell-prompt> echo $?
output: 1
Ответ 7
FWIW, прочитав все остальные ответы, я нашел для меня следующее решение:
Обновление: На самом деле, кто-то обнаружил исправленную с тех пор ошибку в другом ответе, которая также затронула мою. Я думаю, что обновление здесь также является улучшением (см. изменения, если вам интересно).
Это работает для всех сценариев, которые начинаются с #!/bin/bash
, но могут также быть получены из различных оболочек для изучения некоторой информации (например, настроек), которая хранится вне функции main
.
Согласно комментариям ниже, этот ответ здесь, очевидно, не работает для всех вариантов bash
. Также не для систем, где /bin/sh
основан на bash
. I. Т. е. Не удается для bash
v3.x на MacOS. (В настоящее время я не знаю, как решить эту проблему.)
#!/bin/bash
# Function definitions (API) and shell variables (constants) go here
# (This is what might be interesting for other shells, too.)
# this main() function is only meant to be meaningful for bash
main()
{
# The script execution part goes here
}
BASH_SOURCE=".$0" # cannot be changed in bash
test ".$0" != ".$BASH_SOURCE" || main "[email protected]"
Вместо последних двух строк вы можете использовать следующий (на мой взгляд, менее читаемый) код, чтобы не устанавливать BASH_SOURCE
в других оболочках и разрешать set -e
работать в main
:
if ( BASH_SOURCE=".$0" && exec test ".$0" != ".$BASH_SOURCE" ); then :; else main "[email protected]"; fi
Этот скрипт-рецепт имеет следующие свойства:
Если выполняется bash
обычным способом, вызывается main
. Обратите внимание, что это не включает вызов, подобный bash -x script
(где script
не содержит путь), см. ниже.
Если источник bash
, вызывается main
только в том случае, если вызывающий скрипт имеет одно и то же имя. (Например, если он получает источник или через bash -c 'someotherscript "[email protected]"' main-script args..
, где должен быть main-script
, то, что test
видит как $BASH_SOURCE
).
Если источник/выполнен/прочитан/eval
отредактирован оболочкой, отличной от bash
, main
не вызывается (BASH_SOURCE
всегда отличается от $0
).
main
не вызывается, если bash
читает сценарий из stdin, если только вы не установите $0
в качестве пустой строки, например: ( exec -a '' /bin/bash ) <script
При оценке с помощью bash
с eval
(eval "'cat script'"
все кавычки важны!) Из какого-то другого скрипта, это вызывает main
. Если eval
запускается непосредственно из командной строки, это аналогично предыдущему случаю, когда скрипт читается из стандартного ввода. (BASH_SOURCE
пуст, в то время как $0
обычно является /bin/bash
, если не принужден к чему-то совершенно другому.)
Если main
не вызывается, он возвращает true
($?=0
).
Это не зависит от непредвиденного поведения (ранее я писал недокументированное, но я не нашел документации, которую вы не можете unset
или изменить BASH_SOURCE
):
BASH_SOURCE
является зарезервированным для bash массивом. Но если разрешить BASH_SOURCE=".$0"
изменить его, откроется очень опасная банка червей, поэтому я ожидаю, что это не должно иметь никакого эффекта (за исключением, возможно, какого-то уродливого предупреждения, которое появится в какой-то будущей версии bash
).
- Нет документации, что
BASH_SOURCE
работает вне функций. Однако обратное (то, что он работает только в функциях) не задокументировано. Наблюдение состоит в том, что он работает (протестировано с bash
v4.3 и v4.4, к сожалению, у меня больше нет bash
v3.x) и что слишком много сценариев прервется, если $BASH_SOURCE
перестанет работать так, как наблюдается, Поэтому я ожидаю, что BASH_SOURCE
останется таким же, как и для будущих версий bash
.
- В отличие от этого (хорошая находка, кстати!) Рассмотрим
( return 0 )
, который дает 0
, если он получен, и 1
, если он не получен. Это немного неожиданно не только для меня, и (согласно показаниям там) POSIX говорит, что return
от subshell - это неопределенное поведение (а return
здесь явно из подоболочки). Возможно, эта функция в конечном итоге получит достаточно широкое использование, так что ее уже нельзя будет изменить, но AFAICS имеет гораздо более высокий шанс того, что какая-то будущая версия bash
случайно изменит поведение возврата в этом случае.
К сожалению, bash -x script 1 2 3
не запускает main
. (Сравните script 1 2 3
, где у script
нет пути). Следующее может быть использовано в качестве обходного пути:
bash -x "'which script'" 1 2 3
bash -xc '. script' "'which script'" 1 2 3
- То, что
bash script 1 2 3
не запускает main
, можно считать функцией.
Обратите внимание, что ( exec -a none script )
вызывает main
(bash
не передает его $0
в сценарий, для этого вам нужно использовать -c
, как показано в последнем пункте).
Таким образом, за исключением некоторых некоторых angular случаев, main
вызывается только тогда, когда скрипт выполняется обычным способом. Обычно это то, что вам нужно, особенно потому, что в нем отсутствует сложный, трудный для понимания код.
Обратите внимание, что он очень похож на код Python:
if __name__ == '__main__': main()
Что также предотвращает вызов main
, за исключением некоторых angular случаев, так как Вы можете импортировать/загрузить скрипт и применить это __name__='__main__'
Почему я думаю, что это хороший общий способ решения проблемы
Если у вас есть что-то, что может быть получено из нескольких оболочек, это должно быть совместимо. Однако (прочитайте другие ответы), поскольку нет (простого в реализации) переносимого способа обнаружения source
, вы должны изменить правила.
Обеспечив выполнение сценария /bin/bash
, вы точно сделаете это.
Это решает все случаи, кроме следующих, в этом случае сценарий не может быть запущен напрямую:
/bin/bash
не установлен или не работает (т.е. в среде загрузки)
- Если вы передадите его в оболочку, как в
curl https://example.com/script | $SHELL
- (Примечание. Это верно только в том случае, если ваш
bash
достаточно свежий. Сообщается, что этот рецепт не подходит для некоторых вариантов. Поэтому убедитесь, что он работает для вашего случая.)
Однако я не могу думать ни о какой реальной причине, где вам это нужно, а также о возможности параллельно создавать один и тот же сценарий! Обычно вы можете обернуть его, чтобы выполнить main
вручную. Вот так:
$SHELL -c '. script && main'
{ curl https://example.com/script && echo && echo main; } | $SHELL
$SHELL -c 'eval "'curl https://example.com/script'" && main'
echo 'eval "'curl https://example.com/script'" && main' | $SHELL
Примечания
Этот ответ не был бы возможен без помощи всех других ответов! Даже неправильные - которые изначально заставили меня опубликовать это.
Обновление: отредактировано из-за новых открытий, найденных в fooobar.com/questions/50003/...
Ответ 8
Я дам BASH -специфический ответ. Извините. Предположим, что ваше имя script include2.sh
; затем создайте функцию внутри include2.sh
, называемую am_I_sourced
. Здесь моя демонстрационная версия include2.sh
:
am_I_sourced()
{
if [ "${FUNCNAME[1]}" = source ]; then
if [ "$1" = -v ]; then
echo "I am being sourced, this filename is ${BASH_SOURCE[0]} and my caller script/shell name was $0"
fi
return 0
else
if [ "$1" = -v ]; then
echo "I am not being sourced, my script/shell name was $0"
fi
return 1
fi
}
if am_I_sourced -v; then
echo "Do something with sourced script"
else
echo "Do something with executed script"
fi
Теперь попробуйте выполнить его разными способами:
~/toys/bash $ chmod a+x include2.sh
~/toys/bash $ ./include2.sh
I am not being sourced, my script/shell name was ./include2.sh
Do something with executed script
~/toys/bash $ bash ./include2.sh
I am not being sourced, my script/shell name was ./include2.sh
Do something with executed script
~/toys/bash $ . include2.sh
I am being sourced, this filename is include2.sh and my caller script/shell name was bash
Do something with sourced script
Итак, это работает без исключения, и оно не использует хрупкое вещество $_
. Этот трюк использует средство интроспекции BASH, то есть встроенные переменные FUNCNAME
и BASH_SOURCE
; см. их документацию на странице руководства BASH.
Только две оговорки:
1) вызов am_I_called
должен выполняться в источнике script, но не внутри какой-либо функции, иначе ${FUNCNAME[1]}
не возвращает что-то еще. Да... вы могли бы проверить ${FUNCNAME[2]}
- но вы просто делаете свою жизнь труднее.
2) функция am_I_called
должна находиться в исходном script, если вы хотите узнать, какое имя файла будет включено.
Ответ 9
Я хотел бы предложить небольшую поправку на очень полезный ответ Дениса, чтобы сделать его несколько более портативным, надеюсь:
[ "$_" != "$0" ] && echo "Script is being sourced" || echo "Script is a subshell"
потому что [[
не распознается (несколько анальным retentive IMHO) Debian POSIX-совместимой оболочкой, dash
. Кроме того, может потребоваться кавычки для защиты от имен файлов, содержащих пробелы, снова в указанной оболочке.
Ответ 10
Это работает позже в script и не зависит от переменной _:
## Check to make sure it is not sourced:
Prog=myscript.sh
if [ $(basename $0) = $Prog ]; then
exit 1 # not sourced
fi
или
[ $(basename $0) = $Prog ] && exit
Ответ 11
$_
довольно хрупкий. Вы должны проверить это как первое, что вы делаете в script. И даже тогда не гарантировано будет содержать имя вашей оболочки (если она получена) или имя script (если выполнено).
Например, если пользователь установил BASH_ENV
, то в верхней части script, $_
содержится имя последней команды, выполненной в BASH_ENV
script.
Лучший способ, который я нашел, - использовать $0
следующим образом:
name="myscript.sh"
main()
{
echo "Script was executed, running main..."
}
case "$0" in *$name)
main "[email protected]"
;;
esac
К сожалению, этот способ не работает из-за поля в zsh из-за параметра functionargzero
, который делает больше, чем предлагает его имя, и включен по умолчанию.
Чтобы обойти это, я положил unsetopt functionargzero
в мой .zshenv
.
Ответ 12
Я последовал за компактным выражением mklement0.
Это аккуратно, но я заметил, что он может потерпеть неудачу в случае ksh при вызове:
/bin/ksh -c ./myscript.sh
(он думает, что он создан и не потому, что он выполняет подоболочку)
Но выражение будет работать, чтобы обнаружить это:
/bin/ksh ./myscript.sh
Кроме того, даже если выражение компактно, синтаксис несовместим со всеми оболочками.
Итак, я закончил следующим кодом, который работает для bash, zsh, dash и ksh
SOURCED=0
if [ -n "$ZSH_EVAL_CONTEXT" ]; then
[[ $ZSH_EVAL_CONTEXT =~ :file$ ]] && SOURCED=1
elif [ -n "$KSH_VERSION" ]; then
[[ "$(cd $(dirname -- $0) && pwd -P)/$(basename -- $0)" != "$(cd $(dirname -- ${.sh.file}) && pwd -P)/$(basename -- ${.sh.file})" ]] && SOURCED=1
elif [ -n "$BASH_VERSION" ]; then
[[ $0 != "$BASH_SOURCE" ]] && SOURCED=1
elif grep -q dash /proc/$$/cmdline; then
case $0 in *dash*) SOURCED=1 ;; esac
fi
Не стесняйтесь добавлять поддержку экзотических оболочек:)
Ответ 13
Я не думаю, что есть какой-либо переносной способ сделать это как в ksh, так и в bash. В bash вы можете обнаружить его с помощью вывода caller
, но я не думаю, что существует эквивалент в ksh.
Ответ 14
Мне нужен один-лайнер, который работает на [mac, linux] с bash.version >= 3, и ни один из этих ответов не соответствует счету.
[[ ${BASH_SOURCE[0]} = $0 ]] && main "[email protected]"
Ответ 15
Прямо в точку: вы должны оценить, соответствует ли переменная "$ 0" имя вашей оболочки.
Вот так:
#!/bin/bash
echo "First Parameter: $0"
echo
if [[ "$0" == "bash" ]] ; then
echo "The script was sourced."
else
echo "The script WAS NOT sourced."
fi
Через SHELL:
$ bash check_source.sh
First Parameter: check_source.sh
The script WAS NOT sourced.
Через SOURCE:
$ source check_source.sh
First Parameter: bash
The script was sourced.
Очень сложно иметь 100% -ный перенос для обнаружения, если источник script был найден или нет.
Что касается моего опыта (7 лет с Shellscripting), единственный безопасный способ (не полагающийся на переменные окружения с PID и т.д., что небезопасно из-за факт, что это что-то VARIABLE), вы должны:
- расширьте возможности от вашего if
- используя переключатель/кейс, если вы хотите.
Оба параметра не могут быть автоматически масштабированы, но это более безопасный способ.
Например:
когда вы запускаете script через сеанс SSH, значение, возвращаемое переменной "$ 0" (при использовании источника), - bash.
#!/bin/bash
echo "First Parameter: $0"
echo
if [[ "$0" == "bash" || "$0" == "-bash" ]] ; then
echo "The script was sourced."
else
echo "The script WAS NOT sourced."
fi
ИЛИ
#!/bin/bash
echo "First Parameter: $0"
echo
if [[ "$0" == "bash" ]] ; then
echo "The script was sourced."
elif [[ "$0" == "-bash" ]] ; then
echo "The script was sourced via SSH session."
else
echo "The script WAS NOT sourced."
fi
Ответ 16
Я закончил проверкой [[ $_ == "$(type -p "$0")" ]]
if [[ $_ == "$(type -p "$0")" ]]; then
echo I am invoked from a sub shell
else
echo I am invoked from a source command
fi
Когда использовать curl... | bash -s -- ARGS
curl... | bash -s -- ARGS
для запуска удаленного скрипта на лету, $ 0 будет просто bash
вместо обычного /bin/bash
при запуске реального файла скрипта, поэтому я использую type -p "$0"
для отображения полного пути Баш.
тестовое задание:
curl -sSL https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath | bash -s -- /a/b/c/d/e /a/b/CC/DD/EE
source <(curl -sSL https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath)
relpath /a/b/c/d/e /a/b/CC/DD/EE
wget https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath
chmod +x relpath
./relpath /a/b/c/d/e /a/b/CC/DD/EE