Как определить, является ли сценарий источником

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

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

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


person brianegge    schedule 21.04.2010    source источник
comment
У меня была аналогичная проблема некоторое время назад, и я решил ее, избегая «выхода» во всех случаях; kill -INT $$ безопасно завершает скрипт в любом случае.   -  person JESii    schedule 02.03.2018
comment
Вы заметили этот ответ? Дается на 5 лет позже принятого, но батарейки в комплекте.   -  person raratiru    schedule 07.01.2019


Ответы (18)


Это кажется переносимым между Bash и Korn:

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

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

person Dennis Williamson    schedule 21.04.2010
comment
К сожалению, это не гарантирует работу. Если пользователь установил BASH_ENV, $_ в верхней части скрипта будет последней командой, запущенной из BASH_ENV. - person Mikel; 05.04.2011
comment
Это также не будет работать, если вы используете bash для выполнения скрипта, например. $ bash script.sh, тогда $_ будет /bin/bash вместо ./script.sh, как вы ожидаете, когда скрипт вызывается таким образом: $ ./script.sh В любом случае обнаружение с помощью $_ это проблема. - person Wirawan Purwanto; 13.09.2012
comment
Дополнительные тесты могут быть включены для проверки этих методов вызова. - person Dennis Williamson; 19.02.2013
comment
К сожалению, это неправильно! см. мой ответ - person F. Hauri; 11.04.2014
comment
@UpAndAdam: Верно, захвачено или использовано немедленно. Я описал последнее в своем ответе. - person Dennis Williamson; 05.05.2015
comment
Было бы неплохо подчеркнуть этот момент, учитывая важность переносимости в этом вопросе. - person UpAndAdam; 05.05.2015
comment
Подводя итог: хотя этот подход обычно работает, он ненадежен; он терпит неудачу в следующих двух сценариях: (a) bash script (вызов через исполняемый файл оболочки, который это решение неправильно определяет как sourced) и (b) (гораздо менее вероятно) echo bash; . script (если $_ совпадает с оболочки, использующей скрипт, это решение неверно определяет его как подоболочку). Только специальные переменные оболочки (например, $BASH_SOURCE) допускают надежные решения (из этого следует, что не существует надежного решения, совместимого с POSIX). Возможно, хотя и обременительно, создать надежный межоболочный тест. - person mklement0; 02.03.2016
comment
@JPope2014: Ответ в вопросе ОП. Сначала я проверял, $0 == bash, но у этого есть проблемы, если сценарий получен из другого сценария или если пользователь получает его из ksh. - person Dennis Williamson; 30.08.2016
comment
терпит неудачу, когда эхо что-либо используется до этого - person meso_2600; 13.09.2016
comment
@ meso_2600: я говорю, что это должно быть в первой строке сценария в моем ответе. - person Dennis Williamson; 14.09.2016
comment
Это не работает. Я тестировал Ubuntu 14.04 с BASH. - person ivanleoncz; 11.05.2017
comment
У меня не работает. Не должен быть принятым ответом. - person Brian Fitzgerald; 08.03.2021

Если ваша версия 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 ..."
person barroyo    schedule 21.04.2010
comment
Это, возможно, самый чистый способ, поскольку $BASH_SOURCE предназначен именно для этой цели. - person con-f-use; 22.08.2011
comment
Обратите внимание, что это не будет работать под ksh, что является условием, указанным в OP. - person Dennis Williamson; 19.02.2013
comment
Есть ли причина использовать ${BASH_SOURCE[0]} вместо $BASH_SOURCE? И ${0} против $0? - person hraban; 11.01.2016
comment
BASH_SOURCE — это переменная массива (см. руководство), которая содержит трассировку стека источников, где ${BASH_SOURCE[0]} — самый последний. Фигурные скобки используются здесь, чтобы сообщить bash, что является частью имени переменной. Они не нужны для $0 в данном случае, но и не мешают. ;) - person Konrad; 22.01.2016
comment
@Konrad, и если вы развернете $array, вы получите ${array[0]} по умолчанию. Итак, опять же, есть ли причина [...]? - person Charles Duffy; 20.05.2016
comment
Спасибо за меньшее использование -p ‹pattern› — очень полезная опция, которую я пропустил. - person JESii; 02.03.2018
comment
даааа, $_ против $0 не работает, ${BASH_SOURCE[0]} против $0 работает! - person datdinhquoc; 13.10.2018
comment
@CharlesDuffy единственная причина, по которой я могу думать, заключается в том, что $array == ${array[0]} - это странный и неожиданный фрагмент идиосинкразического синтаксиса. Читатель, незнакомый с этим фактом расширения переменных bash, может быть сбит с толку менее явным синтаксисом. - person Luke Griffiths; 17.12.2018
comment
Не уверен, что двойные кавычки в двойных скобках [[]] необходимы. - person Jon Red; 05.05.2021

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

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

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

    • If return is used in the top-level scope of a non-sourced script, an error message is emitted, and the exit code is set to 1.
  • (return 0 2>/dev/null) выполняет return в вложенной оболочке и подавляет сообщение об ошибке; после этого код выхода указывает, был ли сценарий источником (0) или нет (1), который используется с операторами && и || для соответствующей установки переменной sourced.

    • Use of a subshell is necessary, because executing return in the top-level scope of a sourced script would exit the script.
    • Наконечник @Haozhun, который сделал команду более надежной, явно используя 0 в качестве операнда return; он отмечает: в справке bash 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 не демонстрирует.

Таким образом, самый безопасный подход – объединить описанные выше надежные методы для конкретных оболочек с запасным решением для всех оставшихся оболочек.

Наконечник шляпы для Stéphane Desneux и его ответ для вдохновения (преобразование моего выражения оператора кросс-оболочки в 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 для < em>отладка.

person mklement0    schedule 27.02.2015
comment
респект за такой исчерпывающий ответ. - person DrUseful; 21.05.2020
comment
${0##*/} и $() не переносимы по крайней мере в одну старую оболочку Bourne (которую мне все еще приходится использовать, даже если это позорно): echo ${0##*/} дает bad substitution, а echo $(dirname $0) дает syntax error: '(' unexpected. echo $0 дает -sh. - person kbulgrien; 02.10.2020
comment
(возврат 0 2›/dev/null) у меня пока работает - person Brian Fitzgerald; 08.03.2021

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

Поскольку этот вопрос означает ksh и< /strong> bash, в этом есть еще одна часть ответ относительно ksh... см. ниже.

Простой способ bash

[ "$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 сравнение

На всякий случай начинаю писать настоящий скрипт:

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

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

Сложнее: теперь ksh...

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

#!/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: user@pts/4

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

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

person F. Hauri    schedule 11.04.2014
comment
[ "$0" = "$BASH_SOURCE" ] || [ -z "$BASH_SOURCE" ] для скриптов, считываемых через канал (cat script | bash). - person hakre; 25.06.2016
comment
Обратите внимание, что . не является псевдонимом для source, на самом деле все наоборот. source somescript.sh - это Bash-изм и не переносимый, . somescript.sh - это POSIX и переносимый IIRC. - person dragon788; 09.08.2018

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

Самый надежный способ, предложенный Вираваном Пурванто, — это проверить 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 с именами «основной» и «источник» (переопределяет встроенную), эти имена могут появляться в FUNCNAME[], но пока проверяется только последний элемент в этом массиве, двусмысленности нет. .
  • У меня нет хорошего ответа на pdksh. Самое близкое, что я могу найти, относится только к pdksh, где каждый источник сценария открывает новый файловый дескриптор (начиная с 10 для исходного сценария). Почти наверняка не то, на что вы хотите положиться...
person mr.spuratic    schedule 05.02.2013
comment
Как насчет ${FUNCNAME[(( ${#FUNCNAME[@]} - 1 ))]}, чтобы получить последний (нижний) элемент в стеке? Тогда тестирование против основного (отрицательного для OP) было для меня самым надежным. - person Adrian Günter; 27.07.2015
comment
Если у меня есть набор PROMPT_COMMAND, он отображается как последний индекс массива FUNCNAME, если я запускаю source sourcetest.sh. Инвертирование проверки (поиск main в качестве последнего индекса) кажется более надежным: is_main() { [[ ${FUNCNAME[@]: -1} == "main" ]]; }. - person dimo414; 03.05.2017

Примечание редактора. Решение этого ответа работает надежно, но только для 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
person user5754163    schedule 06.01.2016
comment
Это не работает для меня в 0.5.8-2.1ubuntu2 $ readlink $(which sh) dash $ . test.sh This script is sourced. $ ./test.sh This script is sourced. - person Phil Rutschman; 27.09.2016
comment
POSIX не указывает, что return должен делать на верхнем уровне (pubs.opengroup .org/onlinepubs/9699919799/utilities/). Оболочка dash обрабатывает return на верхнем уровне как exit. Другие оболочки, такие как bash или zsh, не позволяют использовать return на верхнем уровне, что является особенностью, которую использует подобный метод. - person user5754163; 03.10.2016
comment
Это работает в sh, если вы удалите $ перед подоболочкой. То есть использовать (return >/dev/null 2>&1) вместо $(return >/dev/null 2>&1) - но тогда это перестанет работать в bash. - person Eponymous; 14.01.2017
comment
@Eponymous: Поскольку dash, где это решение не работает, действует как sh, например, в Ubuntu, это решение обычно не работает с sh. Решение действительно работает для меня в Bash 3.2.57 и 4.4.5 - с $ до (...) или без него (хотя для $ никогда нет веской причины). - person mklement0; 30.03.2017
comment
returning без явного возвращаемого значения прерывается, когда sourceing скрипты сразу после команды с ошибочным выходом. Предложил редактирование улучшения. - person DimG; 05.02.2019
comment
Несмотря на то, что return написан в исходном скрипте, контекст, в котором он вызывается, является высокоуровневым, и поэтому он всегда должен давать сбой. То, что это не так, очевидно, является ошибкой и, следовательно, может быть исправлено в один прекрасный день, после чего это перестанет работать. - person Jan Hudec; 08.10.2019

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

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

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

Согласно комментариям ниже, этот ответ, по-видимому, не работает для всех вариантов bash. Также не для систем, где /bin/sh основано на bash. То есть для 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'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).

  • Если источник/выполнение/чтение/evaled оболочкой, отличной от 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).

  • Это не зависит от неожиданного поведения (ранее я писал undocumented, но я не нашел документации, что вы не можете unset или изменить BASH_SOURCE):

    • BASH_SOURCE is a bash reserved array. But allowing BASH_SOURCE=".$0" to change it would open a very dangerous can of worms, so my expectation is, that this must have no effect (except, perhaps, some ugly warning shows up in some future version of bash).
    • Нет документации, что BASH_SOURCE работает вне функций. Однако обратное (то, что это работает только в функциях) также не задокументировано. Наблюдение состоит в том, что это работает (проверено с bash v4.3 и v4.4, к сожалению, у меня больше нет bash v3.x) и что слишком много скриптов сломается, если $BASH_SOURCE перестанет работать, как наблюдается. Следовательно, я ожидаю, что BASH_SOURCE останется таким же и для будущих версий bash.
    • Напротив (хорошая находка, кстати!) рассмотрим ( return 0 ), что дает 0, если получено, и 1, если нет. Это происходит немного неожиданно не только для меня , и (согласно прочитанному там) POSIX говорит, что return от подоболочка - это неопределенное поведение (и 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 как показано в последнем пункте).

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

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

if __name__ == '__main__': main()

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

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

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

Установив, что скрипт должен выполняться /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

Примечания

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

  • Обновление: отредактировано в связи с новыми открытиями, найденными в https://stackoverflow.com/a/28776166/490291.

person Tino    schedule 02.12.2017
comment
Проверено на кш и баш-4.3. Хороший. Очень жаль, что у вашего ответа будет тяжелая жизнь, учитывая, что другие ответы уже годами собирали голоса. - person hagello; 04.01.2018
comment
спасибо за этот ответ. Я оценил более длинный, «менее читаемый» тест с оператором IF, поскольку приятно обрабатывать обе ситуации, чтобы, по крайней мере, дать не молчаливую ошибку. В моем случае мне нужен источник сценария или иным образом информировать пользователя об их ошибке, связанной с неиспользованием источника. - person Tim Richardson; 22.07.2019
comment
@Tino: As for также может быть получен из разных оболочек: в macOS, где /bin/sh фактически bash в режиме POSIX, назначение BASH_SOURCE ломает ваш скрипт. В других оболочках (dash, ksh, zsh) вызов вашего сценария путем передачи его в качестве аргумента файла непосредственно исполняемому файлу оболочки приводит к сбоям (например, zsh <your-script> заставит ваш сценарий ошибочно подумать, что он получен из источника ). (Вы уже упоминали, что конвейерная обработка кода работает со сбоями во всех оболочках.) - person mklement0; 17.09.2019
comment
@Tino: В качестве отступления: хотя . <your-script> (источник) в принципе работает со всеми POSIX-подобными оболочками, это имеет смысл только в том случае, если сценарий был явно написан для использования только функций POSIX, чтобы предотвратить нарушение функций, характерных для одной оболочки. исполнение в других оболочках; поэтому использование строки shebang Bash (а не #!/bin/sh) сбивает с толку - по крайней мере, без заметного комментария. И наоборот, если ваш скрипт предназначен для запуска только из Bash (даже если только из-за того, что не учитываются функции, которые могут быть непереносимыми), лучше отказаться от выполнения в оболочках, отличных от Bash. - person mklement0; 17.09.2019
comment
@mklement0 к вашему первому комментарию: Запуск bash с export POSIXLY_CORRECT=: В настоящее время я не могу обнаружить никаких проблем. У меня нет macOS, так что спасибо за внимание. И к обоим комментариям: На самом деле я ожидаю, что main написан только для bash и не предназначен для других оболочек (другим оболочкам нужно только анализировать main, а не выполнять его правильно). Таким образом, другие оболочки все еще могут source использовать скрипт, чтобы чему-то научиться у него, например, некоторым общим жестко запрограммированным настройкам (фу!) и т. д. за пределами main (опущены в примере). Это может пригодиться, YMMV - person Tino; 20.09.2019
comment
macOS версии 10.14.6 использует Bash версии 3.2.57, и вы можете воспроизвести проблему следующим образом: bash --posix -c 'echo before; BASH_SOURCE=hi; echo after' (выводит только before); Bash v4.4.23, например, не имеет этой проблемы. Что касается вашего ответа оба комментария: я призываю вас четко указать это в своем ответе, в идеале с измененным кодом, который предотвращает вызов main из других оболочек или, по крайней мере, сначала выдает предупреждение. - person mklement0; 20.09.2019
comment
@mklement0 Отредактировано, спасибо за внимание! Поскольку я понятия не имею, как проверить это самостоятельно, работает ли второй вариант if ( BASH_SOURCE=".$0" && exec test ".$0" != ".$BASH_SOURCE" ); then :; else main "$@"; fi с bash v3.2.57 для macOS? При этом используется подоболочка, поэтому может произойти сбой, следовательно, может выполнить main, как требуется в bash? - person Tino; 27.09.2019
comment
@Tino: подоболочка всегда дает сбой в версии 3.2.57 из-за попытки назначить $BASH_SOURCE, поэтому main всегда выполняется, когда вы вызываете из Bash / Bash, действующий как sh, независимо от того, являетесь ли вы источником сценария или нет . - person mklement0; 27.09.2019
comment
@ mklement0 Еще раз спасибо, добавил примечание о проблеме. Для других читателей: При использовании bash v3.x он не должен выполнять main, но в данном случае он это делает! И когда источником является /bin/sh, то есть bash --posix, в этом случае происходит то же самое, и это тоже совершенно неправильно. - person Tino; 27.09.2019

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

## Check to make sure it is not sourced:
Prog=myscript.sh
if [ $(basename $0) = $Prog ]; then
   exit 1  # not sourced
fi

or

[ $(basename $0) = $Prog ] && exit
person jim mcnamara    schedule 31.05.2010
comment
Я думаю, что этот ответ является одним из немногих, совместимых с POSIX. Очевидным недостатком является то, что вам нужно знать имя файла, и это не работает, если оба скрипта имеют одинаковое имя файла. - person JepZ; 09.04.2019

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

person Wirawan Purwanto    schedule 12.09.2012
comment
Пояснение: для работы этой функции требуется BASH версии 3+. В BASH 2 FUNCNAME — это скалярная переменная, а не массив. Также в BASH 2 нет переменной массива BASH_SOURCE. - person Wirawan Purwanto; 28.09.2012

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

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

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

person user354193    schedule 30.05.2010

$_ довольно хрупкий. Вы должны проверить это как первое, что вы делаете в сценарии. И даже в этом случае не гарантируется, что он будет содержать имя вашей оболочки (если оно получено) или имя скрипта (если оно выполнено).

Например, если пользователь установил BASH_ENV, то в начале скрипта $_ будет содержать имя последней команды, выполненной в скрипте BASH_ENV.

Лучший способ, который я нашел, - использовать $0 следующим образом:

name="myscript.sh"

main()
{
    echo "Script was executed, running main..."
}

case "$0" in *$name)
    main "$@"
    ;;
esac

К сожалению, этот способ не работает из коробки в zsh из-за того, что опция functionargzero делает больше, чем предполагает ее название, и включена по умолчанию.

Чтобы обойти это, я поместил unsetopt functionargzero в свой .zshenv.

person Mikel    schedule 04.04.2011

Я следовал компактному выражению 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

Не стесняйтесь добавлять поддержку экзотических оболочек :)

person Stéphane Desneux    schedule 30.06.2016
comment
В ksh 93+u у меня отлично работает ksh ./myscript.sh (с моим утверждением) - какую версию вы используете? - person mklement0; 29.03.2017
comment
Я боюсь, что нет никакого способа надежно определить, используется ли источник сценария только с использованием POSIX-функций: ваша попытка предполагает Linux (/proc/$$/cmdline) и фокусируется только на dash (который также действует как sh в Ubuntu, для пример). Если вы готовы сделать определенные предположения, вы можете проверить $0 на наличие приемлемого, но неполного, переносимого теста. - person mklement0; 29.03.2017
comment
++ для базового подхода, однако - я взял на себя смелость адаптировать его для того, что я считаю лучшим переносимым приближением поддержки sh/dash, в добавлении к моему ответу. - person mklement0; 29.03.2017
comment
Поскольку упоминается совместимость со всеми оболочками и экзотическими оболочками, ${0##*/} и $() не переносимы по крайней мере на одну старую оболочку Bourne (которую мне все еще приходится использовать, даже если это позор, что это необходимо): echo ${0##*/} дает bad substitution, и echo $(dirname $0) дает syntax error: '(' unexpected. echo $0 дает -sh. - person kbulgrien; 02.10.2020

Я не думаю, что есть какой-либо портативный способ сделать это как в ksh, так и в bash. В bash вы можете обнаружить это, используя вывод caller, но я не думаю, что в ksh существует эквивалент.

person Michal Čihař    schedule 21.04.2010
comment
$0 работает в bash, ksh93 и pdksh. У меня нет ksh88 для проверки. - person Mikel; 05.04.2011

Мне нужен был однострочный вариант, который работает на [mac, linux] с bash.version >= 3, и ни один из этих ответов не подходит.

[[ ${BASH_SOURCE[0]} = $0 ]] && main "$@"
person Karsten    schedule 27.02.2015
comment
Решение bash работает хорошо (можно упростить до $BASH_SOURCE ), но решение ksh ненадежно: если ваш скрипт используется другим скриптом, вы получите ложное срабатывание. - person mklement0; 28.02.2015

Прямо к делу: вы должны оценить, равна ли переменная "$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 check_source.sh
First Parameter: bash

The script was sourced.



Довольно сложно иметь 100% портативный способ определения того, был ли скрипт источником или нет.

Что касается моего опыта (7 лет с Shellscripting), единственный безопасный способ (не полагаться на переменные среды с PID и т. д., что небезопасно из-за того, что является чем-то ПЕРЕМЕННЫМ), вы должны:

  • расширить возможности вашего if
  • используя переключатель / регистр, если хотите.

Оба варианта не могут масштабироваться автоматически, но это более безопасный способ.



Например:

когда вы получаете сценарий через сеанс 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

OR

#!/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
person ivanleoncz    schedule 11.05.2017
comment
Проголосовали против, так как это совершенно неправильно: /bin/bash -c '. ./check_source.sh' дает The script WAS NOT sourced.. Та же ошибка: ln -s /bin/bash pumuckl; ./pumuckl -c '. ./check_source.sh' -> The script WAS NOT sourced. - person Tino; 03.12.2017
comment
Ваш отрицательный голос изменил весь сценарий и внес большой вклад, Тино. Спасибо! - person ivanleoncz; 05.12.2017

В итоге я проверил [[ $_ == "$(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 для запуска удаленного скрипта на лету, $0 будет просто bash вместо обычного /bin/bash при запуске фактического файла скрипта, поэтому я использую type -p "$0", чтобы показать полный путь к bash.

тестовое задание:

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
person osexp2003    schedule 24.07.2018

Это ответвление от некоторых других ответов, касающихся «универсальной» поддержки перекрестной оболочки. По общему признанию, это очень похоже на https://stackoverflow.com/a/2942183/3220983, в частности, хотя и немного отличается. Слабость этого заключается в том, что клиентский скрипт должен учитывать, как его использовать (т.е. сначала экспортировать переменную). Сила в том, что это просто и должно работать «где угодно». Вот шаблон для вашего удовольствия от вырезания и вставки:

# NOTE: This script may be used as a standalone executable, or callable library.
# To source this script, add the following *prior* to including it:
# export ENTRY_POINT="$0"

main()
{
    echo "Running in direct executable context!"
}

if [ -z "${ENTRY_POINT}" ]; then main "$@"; fi

Примечание. Я использую export, просто убедитесь, что этот механизм можно расширить на подпроцессы.

person BuvinJ    schedule 27.11.2019

Решение этой проблемы заключается в том, чтобы не писать код, который должен знать такие вещи, чтобы вести себя правильно. И способ сделать это — поместить код в функцию, а не в основную ветку скрипта, который необходимо получить.

Код внутри функции может быть только return 0 или return 1. Это завершает только функцию, так что управление возвращается к тому, что вызвало функцию.

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

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

. path/to/lib.sh # defines libfunction
libfunction arg

или еще:

path/to/script.sh arg # call script as a child process

и не:

. path/to/script.sh arg  # shell programming anti-pattern
person Kaz    schedule 21.01.2021