Как определить, есть ли 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, есть некоторые проблемы, см. ниже:

Как этот вопрос стоит и , в этом ответе есть еще одна часть, касающаяся ... см. ниже.

Простой 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 много, после некоторых прочитанных на странице 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, но это работает только для одного уровня подсекций...

Извините, я не смог найти надежный способ сделать это под .

Ответ 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