it-swarm-ru.tech

определение пути к исходному сценарию Shell

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

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

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

source foo.bash
86
Cascabel

В 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"
68

Я думаю, что вы могли бы использовать $BASH_SOURCE переменная. Возвращает путь, который был выполнен:

[email protected] ~ $ /home/pbm/a.sh 
/home/pbm/a.sh
[email protected] ~ $ ./a.sh
./a.sh
[email protected] ~ $ source /home/pbm/a.sh 
/home/pbm/a.sh
[email protected] ~ $ source ./a.sh
./a.sh

Поэтому на следующем шаге мы должны проверить, является ли путь относительным или нет. Если это не относительно, все в порядке. Если это так, мы можем проверить путь с помощью pwd, объединить с / а также $BASH_SOURCE.

32
pbm

Это решение относится только к 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]}.

Имя_функции

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

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

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

21
gkb0986

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

$
18
Shawn J. Goff

Это сработало для меня в 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.

16
Paul Brannan

Я был немного смущен ответом вики сообщества (от Шона Дж. Гоффа), поэтому я написал сценарий, чтобы разобраться. Около $_, Я нашел это: Использование _ как переменная окружения, переданная в команду . Это переменная окружения, поэтому легко проверить ее значение неправильно.

Ниже приведен сценарий, затем вывод. Они также находятся в это Gist .

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;[email protected]\e[0m"
}

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

Shell_ARRAY=("[email protected]")

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

  • В зш, $0 имеет то же значение, что и $BASH_SOURCE в баш.

$_

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

Symlinks

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

кШ

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

ш

  • Когда bash или zsh вызывается через символическую ссылку с именем sh, в отношении этих тестов они ведут себя как тире.
2
Mathieu CAROFF

этот ответ описывает, как lsof и ​​немного магии grep - единственная вещь, которая, похоже, имеет шанс работать с вложенными исходными файлами в tcsh:

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

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)
0
JamesThomasMoon1979

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

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

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

0
dols3m

Самая хитрая часть в том, чтобы найти в настоящее время исходный файл для dash Shell, используемого в качестве замены sh в Ubuntu. Следующий фрагмент кода может быть использован в сценарии для определения его абсолютного пути. Протестировано в bash, zsh и dash, которые вызываются как dash и sh.

Примечание: зависит от современной утилиты realpath (1) из пакета GNU coreutils)

NB: опции lsof (1) также должны быть проверены, потому что подобные советы, как на этой, так и на других страницах, не работали для меня на Ubuntu 18 и 19, поэтому мне пришлось заново изобрести это.

getShellName() {
    [ -n "$BASH" ] && echo ${BASH##/*/} && return
    [ -n "$ZSH_NAME" ] && echo $ZSH_NAME && return
    echo ${0##/*/}
}

getCurrentScript() {
    local result
    case "$(getShellName)" in
        bash )  result=${BASH_SOURCE[0]}
                ;;
        zsh )   emulate -L zsh
                result=${funcfiletrace[1]%:*}
                ;;
        dash | sh )
                result=$(
                    lsof -p $$ -Fn  \
                    | tail --lines=1  \
                    | xargs --max-args=2  \
                    | cut --delimiter=' ' --fields=2
                )
                result=${result#n}
                ;;
        * )     result=$0
                ;;
    esac
    echo $(realpath $result)
}
0
maoizm