it-swarm.cn

确定源Shell脚本的路径

是否有sourced Shell脚本找出自身路径的方法?我主要关注bash,尽管我有一些使用tcsh的同事。

我猜这里可能运气不佳,因为采购导致命令在当前Shell中执行,所以$0仍然是当前Shell的调用,而不是源脚本。我目前最好的想法是做source $script $script,以便第一个位置参数包含必要的信息。有人有更好的方法吗?

明确地说,我是sourcing脚本,而不是运行它:

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

在Bash中:

#!/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.shbash 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数组变量中的相应Shell函数名称。 Shell函数$ {FUNCNAME [$ i]}在文件$ {BASH_SOURCE [$ i]}中定义,并从$ {BASH_SOURCE [$ i + 1]}中调用。

功能名称

一个数组变量,包含当前在执行调用堆栈中的所有Shell函数的名称。索引为0的元素是任何当前正在执行的Shell函数的名称。最底部的元素(具有最高索引的元素)是“ main”。仅当执行Shell函数时,此变量才存在。分配给FUNCNAME无效,并返回错误状态。如果未设置FUNCNAME,则即使随后将其重置,它也会失去其特殊属性。

此变量可以与BASH_LINENO和BASH_SOURCE一起使用。 FUNCNAME的每个元素在BASH_LINENO和BASH_SOURCE中都有相应的元素来描述调用堆栈。例如,从文件$ {BASH_SOURCE [$ i + 1]}在行号$ {BASH_LINENO [$ i]}处调用了$ {FUNCNAME [$ i]}。内置呼叫方使用此信息显示当前呼叫堆栈。

[来源:Bash手册]

21
gkb0986

为了彻底和方便搜索者,这是它们的工作...这是一个社区Wiki,因此可以随时添加其他Shell的等效项(显然,$ BASH_SOURCE将有所不同)。

test.sh:

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

test2.sh:

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

重击:

$./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

$

Sh

$ ./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

我对社区Wiki答案(来自Shawn J. Goff)有点困惑,所以我写了一个脚本来整理问题。关于$_,我发现了这一点: 使用_作为传递给命令的环境变量 。这是一个环境变量,因此很容易错误地测试其值。

下面是脚本,然后输出。它们也位于 此要点 中。

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中使用。
  • $0的唯一区别是当前文件是由另一个文件来源的。在这种情况下,$BASH_PROFILE包含源文件的名称,而不是源文件的名称。

$0

  • 在zsh中,$0与bash中的$BASH_SOURCE具有相同的值。

$_

  • $_不受破折号和ksh的影响。
  • 在bash和zsh中,$_衰减到最后一次调用的最后一个参数。
  • bash将$_初始化为“ bash”。
  • zsh保持$_不变。 (采购时,它只是“最后一个论点”规则的结果)。

符号链接

  • 通过符号链接调用脚本时,没有变量包含对链接目标的任何引用,仅包含其名称。

sh

  • 关于这些测试,ksh的行为类似于破折号。

sH

  • 当通过名为sh的符号链接调用bash或zsh时,关于这些测试,其行为类似于破折号。
2
Mathieu CAROFF

此答案 描述了lsof和一点grep魔术是唯一似乎有机会在tcsh下处理嵌套源文件的事情:

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

-tl; drscript=$(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)

但是,如果脚本是run,例如.

$> 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 Shell,我发现 @@ Dennis Williamson的答案 最有用,但是在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]_中获取,而当未定义BASH_SOURCE [0]时,则从${(%):-%x}}中获取。

0
dols3m

最棘手的部分是找到当前源文件是用于在Ubuntu中用作sh替换的破折号Shell。可以在源脚本中使用以下代码片段来确定其绝对路径。在bash中测试,zsh和dash分别作为dash和sh调用。

注意:取决于现代的 realpath(1) 实用工具,来自GNU coreutils软件包

注意: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