Різниця між запуском та джерелом роботи в скрипті bash shell?


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
Джанні Theunissen

Відповіді:


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


Достатньо близько. :) Невелика перешкода, що мені потрібно буде хоча б вказати ім'я сценарію, але я
прийму

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

редагувати Все ще здається більш простим рішенням, якби я просто рахував кількість елементів у 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
}

Масив FUNCNAME, що підтримується Bash, по суті є стеком викликів функцій. $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, як ніби вони є одним тривимірним масивом.


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

Запропоновані рішення порівняння пропонують краще працювати.


0

Один із способів, який також працює при пошуку з інтерактивної оболонки :

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

BASH_LINENOЗмінний також масив з усіма лініями функція виклику була страчена в. Він буде нульовим, якщо ви зателефонуєте безпосередньо до скрипту або до цілого числа, що відповідає номеру рядка.

Документи зі змінною BASH_ *

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.