Различать запуск и получение в сценарии оболочки bash?

22

Либо то, что я здесь спрашиваю, крайне неортодоксально / нетрадиционно / рискованно, либо мои навыки в Google-фу просто не подходят к нулю ...

В bashсценарии оболочки есть какой-нибудь простой способ узнать, получен ли он от другого сценария оболочки, или он запускается сам по себе? Другими словами, возможно ли различить следующие два поведения?

# from another shell script
source myScript.sh

# from command prompt, or another shell script
./myScript.sh

Я думаю о том, чтобы создать подобный утилитам сценарий оболочки, содержащий bashфункции, которые можно сделать доступными при получении. Когда этот скрипт запускается сам по себе, мне бы хотелось, чтобы он выполнял определенные операции, также основываясь на определенных функциях. Существует ли какая-то переменная окружения, которую может использовать этот сценарий оболочки, например

some_function() {
    # ...
}
if [ -z "$IS_SOURCED" ]; then
    some_function;
fi

Предпочтительно, я ищу решение, которое не требует скрипта вызывающей стороны для установки каких-либо флаговых переменных.

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

ХИК
источник
@cuonglm отредактировал мой вопрос, я знаю разницу между ними, но мне интересно, смогу ли я программно заставить сценарий оболочки также показать разницу.
HJK
4
@cuonglm: Не дубликат. Он вообще не спрашивает о .команде, а о том, чтобы определить, был ли скрипт поставлен или работает нормально (т.е. в подоболочке).
Джандер
Очень хорошие ответы на тот же вопрос при переполнении стека: stackoverflow.com/a/28776166/96944
Джанни Теуниссен

Ответы:

19

Да - переменная $ 0 дает имя скрипта во время его запуска:

$ cat example.sh
#!/bin/bash
script_name=$( basename ${0#-} ) #- needed if sourced no path
this_script=$( basename ${BASH_SOURCE} )
if [[ ${script_name} = ${this_script} ]] ; then
    echo "running me directly"
else
    echo "sourced from ${script_name}"
fi 

$ cat example2.sh
#!/bin/bash
. ./example.sh

Который работает как:

$ ./example.sh
running me directly
$ ./example2.sh
example.sh sourced from example2.sh

Это не может служить источником для интерактивной оболочки, но вы поняли эту идею (я надеюсь).

Обновлено, чтобы включить BASH_SOURCE - спасибо, hjk

Темное сердце
источник
Достаточно близко. :) Небольшое препятствие в том, что мне нужно как минимум указать имя скрипта, но я приму этот ответ, если других жизнеспособных решений не будет ...
hjk
7

Комбинация ответа @ DarkHeart с переменной окружения, BASH_SOURCEкажется, добивается цели :

$ head example*.sh
==> example2.sh <==
#!/bin/bash
. ./example.sh

==> example.sh <==
#!/bin/bash
if [ "$(basename $0)" = "$(basename $BASH_SOURCE)" ]; then
    echo "running directly"
else
    echo "sourced from $0"
fi
$ ./example2.sh
sourced from ./example2.sh
$ ./example.sh
running directly

edit Кажется, было бы более простым решением, если бы я просто посчитал количество элементов в BASH_SOURCEмассиве:

if [ ${#BASH_SOURCE[@]} -eq 1 ]; then echo "running directly"; else echo "sourced from $0"; fi
ХИК
источник
1
Похоже, мы нашли переменную 'bash_source' одновременно. :)
DarkHeart
@DarkHeart Я добавил в свой ответ использование подсчета размера массива тоже ... спасибо, что подсказали мне этот путь! : D
HJK
1

Я только что создал такой же библиотечный скрипт, который работает так же, как BusyBox. В нем я использую следующую функцию, чтобы проверить, получен ли он ...

function isSourced () {
  [[ "${FUNCNAME[1]}" == "source" ]]  && return 0
  return 1
}

Поддерживаемый Bash массив FUNCNAME по сути является стеком вызовов функций. $FUNCNAME(или ${FUNCNAME[0]}) - имя выполняемой в данный момент функции. ${FUNCNAME[1]}это имя функции, вызвавшей ее, и так далее.

Самый верхний элемент - это специальное значение для самого скрипта. Это будет содержать ...

  • слово «источник», если сценарий получен
  • слово "main", если скрипт выполняется И тест выполняется внутри функции
  • «» (null), если скрипт выполняется И тест выполняется вне какой-либо функции, то есть ... на уровне самого скрипта.

Функция выше на самом деле работает только при вызове на уровне сценария (это все, что мне нужно). Сбой при вызове из другой функции, потому что номер элемента массива будет неправильным. Чтобы заставить его работать где угодно, нужно найти вершину стека и проверить это значение, что более сложно.

Если вам это нужно, вы можете получить номер элемента массива "вершины стека" с помощью ...

  local _top_of_stack=$(( ${#FUNCNAME[@]} - 1 ))

${#FUNCNAME[@]}это количество элементов в массиве. В качестве массива, начинающегося с нуля, мы вычитаем 1, чтобы получить последний элемент #.

Эти три функции используются вместе для создания трассировки стека функций, аналогичной Python, и они могут дать вам лучшее представление о том, как все это работает ...

function inspFnStack () {
  local T+="  "
  local _at=
  local _text="\n"
  local _top=$(inspFnStackTop)
  local _fn=${FUNCNAME[1]}; [[ $_fn =~ source|main ]]  || _fn+="()"
  local i=_top; ((--i))
  #
  _text+="$i item function call stack for $_fn ...\n"
  _text+="| L   BASH_SOURCE{BASH_LINENO called from}.FUNCNAME  \n"
  _text+="| ---------------------------------------------------\n"
  while (( $i > 0 ))
  do
    _text+="| $i ${T}$(inspFnStackItem $i)\n"
    T+="  "
    ((--i))
  done
  #
  printf "$_text\n"
  #
  return 0
}

function inspFnStackItem ()  {
  local _i=$1
  local _fn=${FUNCNAME[$_i]}; [[ $_fn =~ source|main ]]  || _fn+="()"
  local _at="${BASH_LINENO[$_i-1]}"; [[ $_at == 1 ]]  && _at="trap"
  local _item="${BASH_SOURCE[$_i]}{${_at}}.$_fn"
  #
  printf "%s" "$_item"
  return 0
}

function inspFnStackTop () {
  # top stack item is 1 less than length of FUNCNAME array stack
  printf "%d\n" $(( ${#FUNCNAME[@]} - 1 ))
  #
  return 0
}

Обратите внимание, что FUNCNAME, BASH_SOURCE и BASH_LINENO - это 3 массива, поддерживаемых bash, как если бы они были одним трехмерным массивом.

DocSalvager
источник
0

Просто хочу добавить, что подсчет массива кажется ненадежным, и, вероятно, не следует предполагать, что sourceон использовался, поскольку использование точки ( .) также очень распространено (и предшествует sourceключевому слову).

Например, для sourced.shскрипта, содержащего только echo $0:


$ . sourced.sh 
bash
$ source sourced.sh 
bash
$ chmod +x sourced.sh 
$ ./sourced.sh 
./sourced.sh
$ cat ./sourced.sh 
echo $0

Предлагаемые сравнительные решения работают лучше.

ka1l
источник
0

Один способ, который также работает при поиске из интерактивной оболочки :

if [ $BASH_LINENO -ne 0 ]; then
    some_function;
fi

BASH_LINENOПеременный также массив со всеми линиями функция вызова была казнена в. Он будет равен нулю, если вы вызываете скрипт напрямую, или целое число, соответствующее номеру строки.

Переменная BASH_ * документы

Borisu
источник