Как определить, что сценарий получен - PullRequest
167 голосов
/ 21 апреля 2010

У меня есть скрипт, в котором я не хочу, чтобы он вызывал exit, если он получен.

Я думал о проверке, если $0 == bash, но это имеет проблемы, если сценарий получен из другого сценария или если пользователь получает его из другой оболочки, такой как ksh.

Есть ли надежный способ обнаружения, если сценарий получен?

Ответы [ 16 ]

141 голосов
/ 21 апреля 2010

Если ваша версия 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 ..."
87 голосов
/ 28 февраля 2015

Надежные решения для 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
    

Объяснение:


bash

(return 0 2>/dev/null) && sourced=1 || sourced=0

Примечание: методика была адаптирована из ответа пользователя user5754163 , поскольку он оказался более надежным, чем исходное решение, [[ $0 != "$BASH_SOURCE" ]] && sourced=1 || sourced=0 [1]

  • Bash допускает return операторы только из функций и, вобласть действия верхнего уровня сценария, только если сценарий имеет sourced .

    • Если return используется в области верхнего уровня без источника script, выдается сообщение об ошибке, и код выхода устанавливается на 1.
  • (return 0 2>/dev/null) выполняет return в подоболочке и подавляет сообщение об ошибке;после этого код завершения указывает, был ли скрипт получен (0) или нет (1), что используется с операторами && и || для соответствующей установки переменной sourced.

    • Использование подоболочки необходимо, потому что выполнение return в области верхнего уровня сценария с исходным кодом приведет к выходу из сценария.
    • Наконечник шляпы @ Haozhun , который сделал команду более устойчивой, явно используя 0 в качестве операнда return;он отмечает: для справки по bash return [N]: «Если N опущено, возвращается статус последней команды».В результате более ранняя версия [которая использовала только return без операнда] дает неверный результат, если последняя команда в оболочке пользователя имеет ненулевое возвращаемое значение.

ksh

[[ \
   $(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

[[ $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 -совместимое выражение 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 для отладка .

67 голосов
/ 11 апреля 2014

После прочтения ответа @ DennisWilliamson возникают некоторые проблемы, см. Ниже:

Поскольку этот вопрос обозначает и , в этом ответе есть другая часть, касающаяся ... см. Ниже .

Простой way

[ "$0" = "$BASH_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)

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)

Обратите внимание, что номер процесса не меняется, пока процесс остается sourced :

echo $$
29301

Почему бы не использовать $_ == $0 сравнение

Для обеспечения многих случаев я начинаю писать true скрипт:

#!/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)

Всё в порядке.

Но, для тестирования скрипта перед добавлением -x flag:

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

Сложнее: сейчас ...

Так как я не пользуюсь много, после некоторого прочтения на странице руководства есть мои попытки:

#!/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

В прогоне sourced есть некоторая переменная, но на самом деле ничего не связано ...

Вы можете даже проверить, что $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: user@pts/4

или ps ho cmd $PPID, но эта работа только для одного уровня подразделений ...

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

55 голосов
/ 22 апреля 2010

Это похоже на переносимость между Bash и Korn:

[[ $_ != $0 ]] && echo "Script is being sourced" || echo "Script is a subshell"

Строка, подобная этой, или присваивание, подобное pathname="$_" (с более поздним тестом и действием), должно быть в первой строке сценария или в строке после shebang (которая, если используется, должна быть для ksh для того, чтобы он работал в большинстве случаев).

30 голосов
/ 05 февраля 2013

Ответ BASH_SOURCE[] (bash-3.0 и новее) кажется самым простым, хотя BASH_SOURCE[] не задокументировано для для работы вне тела функции (в настоящее время это работает, не соглашаясь со страницей руководства) ).

Самый надежный способ, предложенный 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 вывода. Вы должны знать или рассчитать локальную глубину вызова, чтобы быть правильной, хотя. Случаи, подобные сценарию, полученному из другой функции или сценария, приведут к тому, что массив (стек) будет глубже. (FUNCNAME - это специальная переменная массива bash, она должна иметь смежные индексы, соответствующие стеку вызовов, если она никогда не равна unset.)

function issourced() {
    [[ ${FUNCNAME[@]: -1} == "source" ]]
}

(В bash-4.2 и более поздних версиях вы можете использовать более простую форму ${FUNCNAME[-1]} вместо последнего элемента в массиве. Улучшено и упрощено благодаря комментарию Денниса Уильямсона ниже.)

Тем не менее, ваша проблема, как указано, заключается в том, что « У меня есть скрипт, в котором я не хочу, чтобы он вызывал« выход », если он получен ». Общая bash идиома для этой ситуации:

return 2>/dev/null || exit

Если сценарий получен, тогда return прервет исходный сценарий и вернется к вызывающей стороне.

Если скрипт выполняется, то return вернет ошибку (перенаправлено), а exit прекратит работу скрипта как обычно. return и exit могут принимать код выхода, если требуется.

К сожалению, это не работает в ksh (по крайней мере, не в производной версии AT & T, которую я здесь имею), он обрабатывает return как эквивалент exit, если он вызывается вне функции или скрипта с точечным источником.

Обновлено : Что вы можете можете сделать в современных версиях ksh, так это проверить специальную переменную .sh.level, которая установлена ​​на глубину вызова функции. Для вызванного сценария это будет изначально не установлено, для сценария с точечным источником будет установлено значение 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, где каждый источник скрипта открывает новый дескриптор файла (начиная с 10 для оригинального скрипта). Почти наверняка это не то, на что вы хотите положиться ...
19 голосов
/ 06 января 2016

Примечание редактора: решение этого ответа работает надежно, но bash -только. Это может быть упрощено до
(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 голосов
/ 03 декабря 2017

FWIW, после прочтения всех остальных ответов, я нашел для меня следующее решение:

Обновление: на самом деле кто-то заметил ошибку в другом ответе , которая также влияет на мой. Я думаю, что обновление здесь также является улучшением (см. Изменения, если вам интересно).

Это работает для всех сценариев, которые начинаются с #!/bin/bash, но могут также быть получены из различных оболочек.

#!/bin/bash

# Function definitions (API) and shell variables (constants) go here

main()
{
# The script's execution part goes here
}

BASH_SOURCE=".$0" # cannot be changed in bash
test ".$0" != ".$BASH_SOURCE" || main "$@"

Вместо последних двух строк вы можете использовать следующий (на мой взгляд, менее читаемый) код, чтобы не устанавливать BASH_SOURCE в других оболочках и разрешать set -e работать в main:

if ( BASH_SOURCE=".$0" && exec test ".$0" != ".$BASH_SOURCE" ); then :; else main "$@"; fi

Этот скрипт-рецепт имеет следующие свойства:

  • Если выполняется bash нормальным способом, вызывается main. Обратите внимание, что это не включает вызов типа bash -x script (где script не содержит путь), см. Ниже.

  • Если источник bash, main вызывается только в том случае, если вызывающий скрипт имеет одно и то же имя. (Например, если он получает источник или через bash -c 'someotherscript "$@"' main-script args.., где main-script должно быть, то, что test видит как $BASH_SOURCE).

  • Если источник / исполняется / читается / eval редактируется оболочкой, отличной от bash, main не вызывается (BASH_SOURCE всегда отличается от $0).

  • main не вызывается, если bash читает скрипт из стандартного ввода, если только вы не установили $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 здесь явно от subshell). Возможно, эта функция в конечном итоге получит достаточно широкое использование, так что ее уже нельзя будет изменить, но 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, как показано в последнем пункте).

Таким образом, за исключением некоторых некоторых угловых случаев, main вызывается только тогда, когда скрипт выполняется обычным способом. Обычно это то, что вы хотите, , особенно потому, что в нем отсутствует сложный, трудный для понимания код.

Обратите внимание, что он очень похож на код Python:

if __name__ == '__main__': main()

Что также предотвращает вызов main, за исключением некоторых угловых случаев, так как Вы можете импортировать / загрузить скрипт и применить это __name__='__main__'

Почему я думаю, что это хороший общий способ решения задачи

Если у вас есть что-то, что может быть получено из нескольких оболочек, оно должно быть совместимым. Однако (прочитайте другие ответы), , поскольку не существует (простого в реализации) переносимого способа обнаружения source ing, вам следует изменить правила .

Обеспечивая выполнение сценария /bin/bash, вы точно делаете это.

Этот решает все случаи, но следует , и в этом случае скрипт не может быть запущен напрямую:

  • /bin/bash не установлен или не работает (т.е. в среде загрузки)
  • Если вы подключите его к оболочке, как в curl https://example.com/script | $SHELL

Однако я не могу думать ни о какой реальной причине, где вам это нужно, а также о возможности параллельно создавать один и тот же сценарий! Обычно вы можете обернуть его, чтобы выполнить main вручную. Вот так:

Примечания

  • Этот ответ был бы невозможен без помощи всех остальных ответов! Даже неправильные - которые изначально заставили меня опубликовать это.

  • Обновление: отредактировано из-за новых открытий, обнаруженных в https://stackoverflow.com/a/28776166/490291

5 голосов
/ 13 сентября 2012

Я дам BASH-специфичный ответ. Корн скорлупа, извините. Предположим, ваше имя скрипта 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 должен иметь место в исходном скрипте, но не в любой функции, иначе ${FUNCNAME[1]} возвращает что-то еще , Да ... ты мог бы проверить ${FUNCNAME[2]} - но ты просто усложнил свою жизнь.

2) функция am_I_called должна находиться в исходном скрипте, если вы хотите узнать, какое имя включаемого файла.

4 голосов
/ 31 мая 2010

Это позже работает в скрипте и не зависит от переменной _:

## 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
4 голосов
/ 31 мая 2010

Я хотел бы предложить небольшую поправку к Очень полезный ответ Денниса , чтобы сделать его немного более портативным, я надеюсь:

[ "$_" != "$0" ] && echo "Script is being sourced" || echo "Script is a subshell"

потому что [[ не распознается (несколько аномально сохраняющимся IMHO) Debian POSIX-совместимой оболочкой , dash. Кроме того, могут потребоваться кавычки для защиты от имен файлов, содержащих пробелы, опять же в указанной оболочке.

...