确定源外壳脚本的路径


80

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

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

为了清楚起见,我正在采购脚本,而不是运行它:

source foo.bash

有4200多个投票的相关问题:stackoverflow.com/q/59895/52074
Trevor Boyd Smith

Answers:


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 2011年

如果脚本是非交互式来源的(例如,来自cshrc),则tcsh版本似乎也不起作用。在这种情况下,我似乎找不到找到信息的方法。有什么想法吗?
卡斯卡贝尔2011年

采购它对我毫无影响。> 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:我发现,我是从2.05b以上测试到4.2.37,包括4.1.9,需要在Bash的所有版本.,并source在这方面同样奏效。请注意,$_必须在文件的第一条语句中访问它,否则它将包含上一条命令的最后一个参数。我喜欢将shebang包括在内以作为我自己的参考,因此我知道它应该用于什么外壳以及用于编辑器,因此它使用语法突出显示。
丹尼斯·威廉姆森

1
哈哈。显然我是先做测试source,然后做测试.。抱歉,我不称职。它们确实是相同的。无论如何,$BASH_SOURCE作品。
clacke 2013年

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则进行搜索/。搜索顺序取决于外壳选项,有关详细信息,请参见手册。
Gilles 2010年

1
那么,类似的东西mydir="$(cd "$(dirname "$BASH_SOURCE")"; pwd)"会起作用吗?
凯文·坎图

谢谢,一个快速而有用的答案。丹尼斯(Dennis)也因提供tcsh答案而赢得绿色复选标记。@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]}中调用。

功能名称

一个数组变量,包含当前在执行调用堆栈中的所有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手册]


这个解决方案对我来说是有效的,而选定的答案只是间歇性地起作用。我从来没有弄清楚为什么有时候它会起作用,而不是其他人(也许我没有对采购外壳给予足够的关注)。
Jim2B 2015年

17

为了彻底和方便搜索者,这是它们的工作...这是一个社区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

$

1
我不明白:为什么called=$_; echo $called; echo $_?此打印不会$_两次吗?
Ciro Santilli新疆改造中心法轮功六四事件

5
@CiroSantilli:并非总是如此,请阅读Bash手册中的$_特殊参数:“在shell启动时,设置为用于调用在环境或参数列表中传递的正在执行的shell或shell脚本的绝对路径名。随后,扩展到最后一个扩展后,前一个命令的参数。还要设置为用于调用执行的每个命令的完整路径名,并放在导出到该命令的环境中。检查邮件时,此参数保存邮件文件的名称。”
亚当·罗森菲尔德2014年

问题在于源文件具有标头#! /bin/sh,使其无法使用。这将启动/bin/sh,设置变量的新实例,然后退出该实例,使调用实例保持不变。
JamesThomasMoon1979

2
@ JamesThomasMoon1979:你在说什么?任何#以Shell脚本开头的内容都是注释。  #!(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

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

下面是脚本,然后输出。他们也在这一要旨中

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

$0

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

$_

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

符号链接

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

sh

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

SH

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

0

对于bash shell,我发现@Dennis Williamson的答案最有帮助,但在的情况下不起作用sudo。这样做:

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

0

要使脚本与bash和zsh兼容,而不是使用if语句,您只需编写即可${BASH_SOURCE[0]:-${(%):-%x}}。结果值将从BASH_SOURCE[0]定义时和${(%):-%x}}未定义BASH_SOURCE [0]时获取。


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)

0

这个答案描述了lsofgrep魔术是如何和似乎是唯一可以在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。


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

您必须准备处理“。” 在某些常见情况下用作目录名称。我会做一些实验,因为我记得当“。”时ksh内置的目录名有所不同。出现在PATH中。


4
这是源脚本,而不是已执行的脚本。$0仅包含用于交互式shell的“ bash”,这就是所有源脚本所看到的。
卡斯卡贝尔
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.