визначення шляху до скрипту джерела оболонки


80

Чи є спосіб, щоб джерело оболонки знайшов шлях до себе? Я в основному переймаюся башем, хоча у мене є співробітники, які використовують tcsh.

Я здогадуюсь, що я, можливо, не матиму тут удачі, оскільки джерело спричиняє виконання команд у поточній оболонці, тому $0все ще викликається поточна оболонка, а не скрипт джерела. В даний час я найкраще думаю source $script $script, щоб перший параметр позиції містив необхідну інформацію. У когось кращий спосіб?

Щоб було ясно, я пошуку сценарій, чи не запустити його:

source foo.bash

Пов'язаний з цим питання , який має 4200 upvotes: stackoverflow.com/q/59895/52074
Trevor Boyd Smith

Відповіді:


65

В tcsh, $_на початку скрипту буде міститися місце, якщо файл було отримано, і $0міститься, якщо він був запущений.

#!/bin/tcsh
set sourced=($_)
if ("$sourced" != "") then
    echo "sourced $sourced[2]"
endif
if ("$0" != "tcsh") then
    echo "run $0"
endif

На Bash:

#!/bin/bash
[[ $0 != $BASH_SOURCE ]] && echo "Script is being sourced" || echo "Script is being run"

Я щойно мав нагоду використати це в tcsh, і помітив, що без shebang це не працює. Здається, трохи не дивно, що поведінка може змінитися, якщо ви просто шукаєте її, а не виконуєте її ...
Cascabel

Версія tcsh також не працює, якщо сценарій створюється неінтерактивно (наприклад, з cshrc). Я не можу знайти спосіб отримати інформацію в цьому випадку. Будь-які думки?
Каскабель

Sourcing це працює для мене без shebang. > tcsh --version\n tcsh 6.14.00 (Astron) 2005-03-25 (i486-intel-linux) options wide,nls,dl,al,kan,rh,nd,color,filec. Що стосується пошуку неінтерактивно, то вихідний файл включається в батьківський файл так, ніби він насправді є його частиною (невідмінно так), як ви згадуєте у своєму початковому запитанні. Я думаю, що ваше вирішення позиційного параметра, мабуть, найкращий підхід. Тим не менш, звичайне питання "чому ти хочеш це зробити", а звичайна відповідь - "не роби цього - роби це замість", де "це" часто зберігати ...
Денніс Вільямсон

2
@clacke: Я вважаю , що у всіх версіях Bash , які я тестував від 2.05b до 4.2.37, включаючи 4.1.9, що .і sourceпрацювали однаково в цьому відношенні. Зауважте, що до нього $_потрібно звернутися в першому операторі у файлі, інакше він буде містити останній аргумент попередньої команди. Мені подобається включати шебанг для власної довідки, тому я знаю, яка оболонка повинна бути для редактора, щоб вона використовувала виділення синтаксису.
Денніс Вільямсон

1
Ха-ха. Очевидно, я тестував спочатку роблячи source, потім роблячи .. Прошу вибачення за некомпетентність. Вони справді однакові. У всякому разі, $BASH_SOURCEпрацює.
clacke

30

Я думаю, що ти можеш використовувати $BASH_SOURCEзмінну. Він повертає шлях, який був виконаний:

pbm@tauri ~ $ /home/pbm/a.sh 
/home/pbm/a.sh
pbm@tauri ~ $ ./a.sh
./a.sh
pbm@tauri ~ $ source /home/pbm/a.sh 
/home/pbm/a.sh
pbm@tauri ~ $ source ./a.sh
./a.sh

Тож на наступному кроці ми повинні перевірити, чи шлях відносний чи ні. Якщо це не відносно, все нормально. Якщо це, ми можемо перевірити шлях pwd, з'єднатись із /та $BASH_SOURCE.


2
І зауважте, що sourceвиконується пошук, $PATHякщо вказане ім’я не містить /. Порядок пошуку залежить від параметрів оболонки, детальну інформацію див. У посібнику.
Жиль

1
Отже, щось подібне mydir="$(cd "$(dirname "$BASH_SOURCE")"; pwd)"спрацювало б?
Кевін Канту

Дякую, швидка та корисна відповідь. Денніс виграє зелений прапорець і за те, що він дав відповідь. @Gilles: Так, я знайшов це в документації. На щастя, у випадку мого використання я майже напевно не повинен про це турбуватися.
Каскабель

17

Це рішення стосується лише bash, а не tcsh. Зауважте, що поширений відповідь ${BASH_SOURCE[0]}не буде працювати, якщо ви спробуєте знайти шлях із функції.

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

echo ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}

Якщо ви хочете дотримуватися посилання, використовуйте readlinkна шляху, який ви переходите вище, рекурсивно чи не рекурсивно.

Ось сценарій, щоб випробувати його та порівняти його з іншими запропонованими рішеннями. Викликати це як source test1/test2/test_script.shабо bash test1/test2/test_script.sh.

#
# Location: test1/test2/test_script.sh
#
echo $0
echo $_
echo ${BASH_SOURCE}
echo ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}

cur_file="${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}"
cur_dir="$(dirname "${cur_file}")"
source "${cur_dir}/func_def.sh"

function test_within_func_inside {
    echo ${BASH_SOURCE}
    echo ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}
}

echo "Testing within function inside"
test_within_func_inside

echo "Testing within function outside"
test_within_func_outside

#
# Location: test1/test2/func_def.sh
#
function test_within_func_outside {
    echo ${BASH_SOURCE}
    echo ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}
}

Причина роботи однолінійки пояснюється використанням BASH_SOURCEзмінної середовища та її асоційованого елемента FUNCNAME.

BASH_SOURCE

Змінна масиву, членами якої є імена файлів джерела, де визначені відповідні імена функцій оболонки в змінній масиву FUNCNAME. Функція оболонки $ {FUNCNAME [$ i]} визначена у файлі $ {BASH_SOURCE [$ i]} і викликана від $ {BASH_SOURCE [$ i + 1]}.

FUNCNAME

Змінна масиву, що містить імена всіх функцій оболонки, що знаходяться в стеці виклику виконання. Елемент з індексом 0 - назва будь-якої функції оболонки, яка виконується в даний час. Найнижчий елемент (той, що має найвищий показник) - "головний". Ця змінна існує лише тоді, коли виконується функція оболонки. Призначення FUNCNAME не мають ефекту і повертають статус помилки. Якщо FUNCNAME не встановлено, він втрачає свої особливі властивості, навіть якщо він згодом скидається.

Цю змінну можна використовувати з BASH_LINENO та BASH_SOURCE. Кожен елемент FUNCNAME має відповідні елементи в BASH_LINENO та BASH_SOURCE для опису стеку викликів. Наприклад, $ {FUNCNAME [$ i]} викликався з файлу $ {BASH_SOURCE [$ i + 1]} за номером рядка $ {BASH_LINENO [$ i]}. Вбудований абонент відображає поточний стек викликів за допомогою цієї інформації.

[Джерело: Посібник Баша]


Це рішення спрацювало для мене, коли вибрана відповідь працювала лише з перервами. Я ніколи не з'ясовував, чому це працює іноді, а не інші (можливо, я не приділяв належної уваги оболонці джерела).
Jim2B

17

Для ретельності та ради шукачів, ось що вони роблять ... Це вікі спільноти, тому сміливо додайте інші еквіваленти оболонки (очевидно, $ BASH_SOURCE буде іншим).

test.sh:

#! /bin/sh
called=$_
echo $called
echo $_
echo $0
echo $BASH_SOURCE

test2.sh:

#! /bin/sh
source ./test.sh

Bash:

$./test2.sh
./test2.sh
./test2.sh
./test2.sh
./test.sh
$ sh ./test2.sh
/bin/sh
/bin/sh
./test2.sh
./test.sh

Тире

$./test2.sh
./test2.sh
./test2.sh
./test2.sh

$/bin/sh ./test2.sh
/bin/sh
/bin/sh
./test2.sh

$

Зш

$ ./test2.sh
./test.sh
./test.sh
./test.sh

$ zsh test.sh

echo
test.sh

$

1
Я не розумію: чому called=$_; echo $called; echo $_? Не буде цього друку $_двічі?
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

5
@CiroSantilli: Не завжди читайте посібник Bash про $_спеціальний параметр: "При запуску оболонки встановіть абсолютне ім'я шляху, яке використовується для виклику сценарію оболонки або оболонки, виконаного як передано в середовищі або списку аргументів. Згодом розширюється до останнього після розширення. Також встановіть повне ім'я шляху, яке використовується для виклику кожної команди, виконаної та розміщеної в середовищі, експортованому до цієї команди. Під час перевірки пошти цей параметр містить ім'я файлу пошти. "
Адам Розенфілд

Проблема в цьому полягає в тому, що джерело файлу має заголовок, #! /bin/shякий робить його марним для джерела. Це запустить новий екземпляр /bin/sh, встановить змінні, а потім вийде з цього примірника, залишивши викликає екземпляр незмінним.
JamesThomasMoon1979

2
@ JamesThomasMoon1979: Про що ти говориш? Все, що починається з #сценарію оболонки - це коментар.  #!(shebang) має своє особливе значення лише як перший рядок сценарію, який виконується.   У першому рядку файлу , який соерсед, це просто коментар.
Скотт

13

Це працювало для мене в bash, dash, ksh та zsh:

if test -n "$BASH" ; then script=$BASH_SOURCE
elif test -n "$TMOUT"; then script=${.sh.file}
elif test -n "$ZSH_NAME" ; then script=${(%):-%x}
elif test ${0##*/} = dash; then x=$(lsof -p $$ -Fn0 | tail -1); script=${x#n}
else script=$0
fi

echo $script

Вихід для цих снарядів:

BASH source: ./myscript
ZSH source: ./myscript
KSH source: /home/pbrannan/git/theme/src/theme/web/myscript
DASH source: /home/pbrannan/git/theme/src/theme/web/myscript
BASH: ./myscript
ZSH: ./myscript
KSH: /home/pbrannan/git/theme/src/theme/web/myscript
DASH: ./myscript

Я намагався змусити його працювати для csh / tcsh, але це занадто важко; Я дотримуюся POSIX.


1

Мене трохи збентежила відповідь на вікі спільноти (від Шона Дж. Гоффа), тому я написав сценарій, щоб розібратися. О $_, я виявив таке: Використання _змінної середовища передається команді . Це змінна середовище, тому легко перевірити його значення неправильно.

Нижче сценарій, потім його вихід. Вони теж є в цій суті .

test-shell-default-variables.sh

#!/bin/bash

# test-shell-default-variables.sh

# Usage examples (you might want to `sudo apt install zsh ksh`):
#
#  ./test-shell-default-variables.sh dash bash
#  ./test-shell-default-variables.sh dash bash zsh ksh
#  ./test-shell-default-variables.sh dash bash zsh ksh | less -R

# `-R` in `less -R` to have less pass escape sequences directly to the terminal
# so we have colors.


# The "invoking with name `sh`" tests are commented because for every shell I
# tested (dash, bash, zsh and ksh), the output was the same as that of dash.

# The `test_expression` function also work with expansion changes. You can try
# lines like `test_expression '{BASH_SOURCE:-$0}'`.

echolor() {
    echo -e "\e[1;36m$@\e[0m"
}

tell_file() {
    echo File \`"$1"\` is:
    echo \`\`\`
    cat "$1"
    echo \`\`\`
    echo
}

SHELL_ARRAY=("$@")

test_command() {
    for shell in "${SHELL_ARRAY[@]}"
    do
        prepare "$shell"
        cmd="$(eval echo $1)"
        # echo "cmd: $cmd"
        printf '%-4s: ' "$shell"
        { env -i $cmd 2>&1 1>&3 | sed 's/^/[err]/'; } 3>&1
        teardown
    done
    echo
}

prepare () {
    shell="$1"
    PATH="$PWD/$shell/sh:$PATH"
}

teardown() {
    PATH="${PATH#*:}"
}


###
### prepare
###
for shell in "${SHELL_ARRAY[@]}"
do
    mkdir "$shell"
    ln -sT "/bin/$shell" "$shell/sh"
done

echo > printer.sh
echo '. ./printer.sh' > sourcer.sh
rm linked.sh &>/dev/null; ln -sT "printer.sh" "linked.sh"

tell_file sourcer.sh

###
### run
###
test_expression() {
    local expr="$1"

    # prepare
    echo "echo $expr" > printer.sh
    tell_file printer.sh

    # run
    cmd='$shell ./printer.sh'
    echolor "\`$cmd\` (simple invocation) ($expr):"
    test_command "$cmd"

    # cmd='sh ./printer.sh'
    # echolor "\`$cmd\` (when executable name is \`sh\`) ($expr):"
    # test_command "$cmd"

    cmd='$shell ./sourcer.sh'
    echolor "\`$cmd\` (via sourcing) ($expr):"
    test_command "$cmd"

    # cmd='sh ./sourcer.sh'
    # echolor "\`$cmd\` (via sourcing, when name is \`sh\`) ($expr):"
    # test_command "$cmd"

    cmd='$shell ./linked.sh'
    echolor "\`$cmd\` (via symlink) ($expr):"
    test_command "$cmd"

    # cmd='sh ./linked.sh'
    # echolor "\`$cmd\` (via symlink, when name is \`sh\`) ($expr):"
    # test_command "$cmd"

    echolor "------------------------------------------"
    echo
}

test_expression '$BASH_SOURCE'
test_expression '$0'
test_expression '$(/bin/true x y; true a b c; echo $_)' # Rq: true is a builtin
test_expression '$_'

###
### teardown
###
for shell in "${SHELL_ARRAY[@]}"
do
    rm "$shell/sh"
    rm -d "$shell"
done

rm sourcer.sh
rm linked.sh
rm printer.sh

Вихід ./test-shell-default-variables.sh {da,ba,z,k}sh

File `sourcer.sh` is:
```
. ./printer.sh
```

File `printer.sh` is:
```
echo $BASH_SOURCE
```

`$shell ./printer.sh` (simple invocation) ($BASH_SOURCE):
dash: 
bash: ./printer.sh
zsh : 
ksh : 

`$shell ./sourcer.sh` (via sourcing) ($BASH_SOURCE):
dash: 
bash: ./printer.sh
zsh : 
ksh : 

`$shell ./linked.sh` (via symlink) ($BASH_SOURCE):
dash: 
bash: ./linked.sh
zsh : 
ksh : 

------------------------------------------

File `printer.sh` is:
```
echo $0
```

`$shell ./printer.sh` (simple invocation) ($0):
dash: ./printer.sh
bash: ./printer.sh
zsh : ./printer.sh
ksh : ./printer.sh

`$shell ./sourcer.sh` (via sourcing) ($0):
dash: ./sourcer.sh
bash: ./sourcer.sh
zsh : ./printer.sh
ksh : ./sourcer.sh

`$shell ./linked.sh` (via symlink) ($0):
dash: ./linked.sh
bash: ./linked.sh
zsh : ./linked.sh
ksh : ./linked.sh

------------------------------------------

File `printer.sh` is:
```
echo $(/bin/true x y; true a b c; echo $_)
```

`$shell ./printer.sh` (simple invocation) ($(/bin/true x y; true a b c; echo $_)):
dash: 
bash: c
zsh : c
ksh : 

`$shell ./sourcer.sh` (via sourcing) ($(/bin/true x y; true a b c; echo $_)):
dash: 
bash: c
zsh : c
ksh : 

`$shell ./linked.sh` (via symlink) ($(/bin/true x y; true a b c; echo $_)):
dash: 
bash: c
zsh : c
ksh : 

------------------------------------------

File `printer.sh` is:
```
echo $_
```

`$shell ./printer.sh` (simple invocation) ($_):
dash: 
bash: bash
zsh : 
ksh : 

`$shell ./sourcer.sh` (via sourcing) ($_):
dash: 
bash: bash
zsh : ./printer.sh
ksh : 

`$shell ./linked.sh` (via symlink) ($_):
dash: 
bash: bash
zsh : 
ksh : 

------------------------------------------

Що ми дізналися?

$BASH_SOURCE

  • $BASH_SOURCE працює в bash і тільки в bash.
  • Єдина відмінність у $0тому, коли поточний файл отриманий іншим файлом. У цьому випадку $BASH_PROFILEміститься ім'я файла, що розміщується, а не ім'я файлу, що розміщується.

$0

  • У zsh $0має те саме значення, що і $BASH_SOURCEв bash.

$_

  • $_ залишається недоторканим тире і кш.
  • У bash і zsh $_відхиляється до останнього аргументу останнього дзвінка.
  • bash ініціалізується $_на "bash".
  • zsh листя $_недоторкані. (при пошуку, це лише результат правила "останнього аргументу").

Символьні посилання

  • Коли скрипт викликається через симпосилання, жодна змінна не містить посилання на призначення посилання, лише його ім'я.

кш

  • Щодо цих тестів, ksh поводиться як тире.

ш

  • Коли bash або zsh викликається через назване символьне посилання shстосовно цих тестів, він поводиться як тире.

0

Що стосується оболонки баш, я знайшов відповідь @Dennis Williamson найбільш корисною, але це не спрацювало у випадку sudo. Це робить:

if ( [[ $_ != $0 ]] && [[ $_ != $SHELL ]] ); then
    echo "I'm being sourced!"
    exit 1
fi

0

Щоб зробити ваш сценарій як bash-, так і zsh-сумісним замість того, щоб використовувати, якщо заяви ви можете просто написати ${BASH_SOURCE[0]:-${(%):-%x}}. Отримане значення буде взято, BASH_SOURCE[0]коли воно визначене, і ${(%):-%x}}коли BASH_SOURCE [0] не визначено.


0

tl; dr script=$(readlink -e -- "${BASH_SOURCE}") (для баш очевидно)


$BASH_SOURCE тестові справи

заданий файл /tmp/source1.sh

echo '$BASH_SOURCE '"(${BASH_SOURCE})"
echo 'readlink -e $BASH_SOURCE'\
     "($(readlink -e -- "${BASH_SOURCE}"))"

source файл різними способами

source з /tmp

$> cd /tmp

$> source source1.sh
$BASH_SOURCE (source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)

$> source ./source1.sh
$BASH_SOURCE (./source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)

$> source /tmp/source1.sh
$BASH_SOURCE (/tmp/source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)

source з /

cd /
$> source /tmp/source1.sh
$0 (bash)
$BASH_SOURCE (/tmp/source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)

sourceз різних відносних шляхів /tmp/aі/var

$> cd /tmp/a

$> source ../source1.sh
$BASH_SOURCE (../source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)

$> cd /var

$> source ../tmp/source1.sh
$BASH_SOURCE (../tmp/source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)

щодо $0

у всіх випадках, якщо сценарій мав додану команду

echo '$0 '"(${0})"

тоді sourceсценарій завжди друкується

$0 (bash)

однак , якщо сценарій був запущений , напр

$> bash /tmp/source1.sh

тоді $0буде значення рядка /tmp/source1.sh.

$0 (/tmp/source1.sh)
$BASH_SOURCE (/tmp/source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)

0

ця відповідь описує, як lsofі трохи греп-магії - це єдине, що, здається, має шанс працювати для вкладених файлів, що знаходяться під tcsh:

/usr/sbin/lsof +p $$ | grep -oE /.\*source_me.tcsh

-2
wdir="$PWD"; [ "$PWD" = "/" ] && wdir=""
case "$0" in
  /*) scriptdir="${0%/*}";;
  *) scriptdir="$wdir/${0#./}"; scriptdir="${scriptdir%/*}";;
esac
echo "$scriptdir"

Можливо, це не працюватиме із символьними посиланнями чи джерелами файлів, але для нормальних файлів. Взято за посилання сюди. @kenorb Немає ім’я, читати посилання, BASH_SOURCE.


1
Це було пояснено в питанні , який $0отримує вам інформацію про себе працює скрипт, а не отримані один.
Скотт

-3

Насправді "dirname $ 0" отримає вам шлях до сценарію, але вам доведеться його трохи інтерпретувати:

$ cat bash0
#!/bin/bash
echo \$0=$0
dirname $0
$ bash0    # "." appears in PATH right now.
$0=./bash0
.
$ ./bash0
$0=./bash0
.
$ $PWD/bash0
$0=/home/00/bediger/src/ksh/bash0
/home/00/bediger/src/ksh
$ $PWD/../ksh/bash0
$0=/home/00/bediger/src/ksh/../ksh/bash0
/home/00/bediger/src/ksh/../ksh
$ ../ksh/bash0
$0=../ksh/bash0
../ksh

Ви повинні підготуватися до обробки "". як ім'я каталогу за деяких загальних обставин. Я б трохи експериментував, оскільки пам'ятаю вбудовану dirname, щоб ksh робив речі дещо інакше, коли ". з'являється в PATH.


4
Це скрипт із джерелами, а не виконаний. $0просто містить "bash" для інтерактивної оболонки, і це все, що бачить скрипт.
Каскабель
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.