определение пути к исходному скрипту

80

Есть ли способ для Sourced скрипт , чтобы узнать путь к себе? В основном я занимаюсь bash, хотя у меня есть коллеги, которые используют tcsh.

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

Чтобы было ясно, я поиске сценарий, не запустить его:

source foo.bash
Cascabel
источник
связанный вопрос, который имеет более 4200 голосов: stackoverflow.com/q/59895/52074
Тревор Бойд Смит

Ответы:

65

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

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

В Баше:

#!/bin/bash
[[ $0 != $BASH_SOURCE ]] && echo "Script is being sourced" || echo "Script is being run"
Деннис Уильямсон
источник
Я только что имел возможность использовать это в tcsh, и заметил, что это не работает без Шебанга. Кажется немного странным, что поведение меняется, если вы просто используете его, а не выполняете ...
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работали одинаково в этом отношении. Обратите внимание, что к нему $_нужно обращаться в первом операторе файла, иначе он будет содержать последний аргумент предыдущей команды. Мне нравится включать shebang для моей собственной ссылки, чтобы я знал, для какой оболочки он предназначен, и для редактора, чтобы он использовал подсветку синтаксиса.
Деннис Уильямсон
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)"бы сработало?
Кевин Канту
Спасибо, быстрый и полезный ответ. Деннис выигрывает зеленую галочку за то, что дал ответ tcsh. @ Жиль: Да, я нашел это в документации. К счастью для моего случая использования, я почти наверняка не должен волноваться об этом.
Каскабель
18

Для тщательности и ради поисковиков, вот что они делают ... Это вики сообщества, так что не стесняйтесь добавлять эквиваленты других оболочек (очевидно, $ 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

$

Zsh

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

$ zsh test.sh

echo
test.sh

$
Shawn J. Goff
источник
1
Я не понимаю: почему called=$_; echo $called; echo $_? Разве этот отпечаток не будет $_дважды?
Сиро Сантилли 新疆 改造 中心 法轮功 六四 事件
5
@CiroSantilli: не всегда читайте руководство Bash по $_специальному параметру: «При запуске оболочки задайте абсолютный путь, используемый для вызова оболочки или сценария оболочки, выполняемого в том виде, как он был передан в среде или списке аргументов. Затем расширяется до последнего аргумент предыдущей команды, после раскрытия. Также задайте полный путь, используемый для вызова каждой команды, выполненной и помещенной в среду, экспортируемую в эту команду. При проверке почты этот параметр содержит имя почтового файла. "
Адам Розенфилд
Проблема #! /bin/shв том, что исходный файл имеет заголовок, что делает его бесполезным для источника. Это будет запускать новый экземпляр /bin/sh, устанавливать переменные, а затем выходить из этого экземпляра, оставляя вызывающий экземпляр без изменений.
JamesThomasMoon1979
2
@ JamesThomasMoon1979: О чем ты говоришь? Все, что начинается #в сценарии оболочки, является комментарием.  #!(Шебанг) ​​имеет свое особое значение только в качестве первой строки исполняемого скрипта .   В качестве первой строки файла, который получен, это просто комментарий.
Скотт
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]}.

имя_функции

Переменная массива, содержащая имена всех функций оболочки, находящихся в данный момент в стеке вызовов выполнения. Элемент с индексом 0 - это имя любой выполняемой в данный момент функции оболочки. Самый нижний элемент (с наивысшим индексом) является «основным». Эта переменная существует только при выполнении функции оболочки. Назначения FUNCNAME не имеют никакого эффекта и возвращают статус ошибки. Если FUNCNAME не установлено, оно теряет свои специальные свойства, даже если оно впоследствии сбрасывается.

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

[Источник: руководство Bash]

gkb0986
источник
Это решение работало для меня в bash, в то время как выбранный ответ работал только периодически. Я никогда не понимал, почему это иногда работает, а не другие (возможно, я не обращал достаточно внимания на оболочку источников).
Jim2B
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 оставляет $_нетронутым. (при поиске это просто результат правила "последнего аргумента").

Symlinks

  • Когда скрипт вызывается через символическую ссылку, ни одна переменная не содержит никакой ссылки на назначение ссылки, только ее имя.

КШ

  • Что касается этих тестов, ksh ведет себя как тире.

ш

  • Когда bash или zsh вызывается через символическую ссылку с именем sh, относительно этих тестов, они ведут себя как dash.
Матье КАРОФФ
источник
0

Для оболочки bash я нашел ответ @Dennis Williamson наиболее полезным, но в случае он не сработал sudo. Это делает:

if ( [[ $_ != $0 ]] && [[ $_ != $SHELL ]] ); then
    echo "I'm being sourced!"
    exit 1
fi
Matt
источник
0

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

dols3m
источник
0

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


$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)
JamesThomasMoon1979
источник
0

Этот ответ описывает, как lsofи немного магии grep - единственная вещь, которая, кажется, имеет шанс работать с вложенными исходными файлами в 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 Нет имени, readlink, BASH_SOURCE.

HemanthJabalpuri
источник
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» для интерактивной оболочки, и это все, что видит сценарий.
Каскабель