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

我想我可能不太幸运,因为采购导致命令在当前shell中执行,因此source $script $script仍然是当前Shell的调用,而不是源脚本。目前,我最好的想法是执行q4312079q,以便第一个位置参数包含必要的信息。任何人都有更好的方法吗?

要清楚,我正在采购脚本,而不是运行它:

source foo.bash


评论

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

#1 楼

tcsh中,脚本开头的$_将包含该位置(如果该文件是源文件),而q4312079q包含该位置(如果该文件已运行)。
/>
#!/bin/tcsh
set sourced=($_)
if ("$sourced" != "") then
    echo "sourced $sourced[2]"
endif
if ("
#!/bin/bash
[[ q4312078q != $BASH_SOURCE ]] && echo "Script is being sourced" || echo "Script is being run"
" != "tcsh") then echo "run q4312078q" endif


评论


我只是有机会在tcsh中使用它,并且注意到没有shebang它将无法正常工作。如果您只是采购而不执行,那么行为改变似乎有些奇怪...

–卡斯卡贝尔
2011年4月20日在16:28

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

–卡斯卡贝尔
2011年5月9日18:04

采购它对我没有任何影响。 > tcsh --version \ n tcsh 6.14.00(Astron)2005-03-25(i486-intel-linux)选项宽,nls,dl,al,kan,rh,nd,color,filec。至于非交互地采购它,就像您在原始问题中提到的那样,源文件被包含在父文件中,就好像它实际上是它的一部分一样。我认为您的位置参数解决方法可能是最好的方法。但是,通常的问题是“您为什么要这样做”,而答复的通常的答案是“不要这样做-而是这样做”,其中“此”通常存储在...

–丹尼斯·威廉姆森
2011年5月10日15:33

@clacke:我发现从2.05b到4.2.37(包括4.1.9)测试的所有Bash版本中都存在该问题。在这方面,消息来源的工作方式完全相同。请注意,必须在文件的第一条语句中访问$ _,否则它将包含上一条命令的最后一个参数。我喜欢将shebang包括在内以作为我自己的参考,因此我知道它应该用于什么外壳以及用于编辑器,因此它使用语法突出显示。

–丹尼斯·威廉姆森
13年4月4日在11:51

哈哈。显然,我正在通过先进行源测试,然后再进行测试来进行测试。它们确实是相同的。无论如何,$ BASH_SOURCE起作用。

–clacke
2013年5月5日7:35



#2 楼

我认为您可以使用$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串联。

评论


并请注意,如果给定名称不包含/,则源将在$ PATH中搜索。搜索顺序取决于外壳选项,有关详细信息,请参见手册。

–吉尔斯'所以-不再是邪恶的'
2010-12-8 19:09

因此,类似mydir =“ $(cd” $(dirname“ $ BASH_SOURCE”)“; pwd)”的东西可以工作吗?

–凯文·坎图(Kevin Cantu)
2010-12-8 19:36

谢谢,一个快速而有用的答案。丹尼斯(Dennis)也因提供tcsh答案而赢得绿色复选标记。 @Gilles:是的,我确实在文档中找到了。幸运的是,对于我的用例,我几乎不必担心它。

–卡斯卡贝尔
2010-12-8 19:46

#3 楼

此解决方案仅适用于bash,不适用于tcsh。请注意,如果您尝试从函数中查找路径,则通常提供的答案${BASH_SOURCE[0]}将不起作用。

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


如果要跟随符号链接,请在到达的路径上使用readlink(递归或非递归)。

这里是一个脚本,可以对其进行尝试并将其与其他建议的解决方案进行比较。调用它为source test1/test2/test_script.shbash test1/test2/test_script.sh

BASH_SOURCE

一个数组变量,其成员是源文件名,在此文件名中定义了FUNCNAME数组变量中的相应外壳函数名称。 shell函数$ {FUNCNAME [$ i]}在文件$ {BASH_SOURCE [$ i]}中定义,并从$ {BASH_SOURCE [$ i + 1]}调用。

FUNCNAME

一个数组变量,包含当前在执行调用堆栈中的所有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年9月9日15:45

#4 楼

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

#! /bin/sh
called=$_
echo $called
echo $_
echo 
#! /bin/sh
source ./test.sh
echo $BASH_SOURCE


test2.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

$


Dash

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

$ zsh test.sh

echo
test.sh

$


Zsh

q4312078q

评论


我不明白:为什么叫= $ _;回声$ call;回声$ _?不会两次打印$ _吗?

– Ciro Santilli郝海东冠状病六四事件法轮功
2014-09-14 7:17



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

–亚当·罗森菲尔德
2014-09-17 19:14



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

–JamesThomasMoon1979
19 Mar 6 '19 at 5:39

@ JamesThomasMoon1979:你在说什么? ashell脚本中以#开头的任何内容都是注释。 #! (shebang)仅在执行脚本的第一行时才具有特殊含义。作为源文件的第一行,它只是一个注释。

–斯科特
19年6月9日在4:17

#5 楼

在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=
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
fi echo $script


这些shell的输出:

q4312078q

我试图使其适用于csh / tcsh,但这太难了;我坚持使用POSIX。

#6 楼

我对社区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:-
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 q4312078q
```

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

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

`$shell ./linked.sh` (via symlink) (q4312078q):
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 : 

------------------------------------------
}'`. echolor() { echo -e "\e[1;36m$@\e[0m" } tell_file() { echo File \`""\` is: echo \`\`\` cat "" echo \`\`\` echo } SHELL_ARRAY=("$@") test_command() { for shell in "${SHELL_ARRAY[@]}" do prepare "$shell" cmd="$(eval echo )" # echo "cmd: $cmd" printf '%-4s: ' "$shell" { env -i $cmd 2>&1 1>&3 | sed 's/^/[err]/'; } 3>&1 teardown done echo } prepare () { shell="" 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="" # 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 'q4312078q' 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




q4312078q

我们学到了什么?

$BASH_SOURCE



$BASH_SOURCE只能在bash中使用。
$BASH_PROFILE唯一的区别是当前文件是由另一个文件提供的。在这种情况下,$BASH_SOURCE包含源文件的名称,而不是源文件的名称。

$_


在zsh中,$_在bash中具有与$_相同的值。

$_



$_不受破折号和ksh的影响。
在bash和zsh,sh衰减到最后一次调用的最后一个参数。
bash将q4312079q初始化为“ bash”。
zsh使q4312079q保持不变。 “最后一个参数”规则)。

符号链接


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

ksh


关于这些测试,ksh的行为就像破折号。

sh


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


#7 楼

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

#8 楼

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

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


#9 楼

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


$> 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)




/tmp来自source


cd /
$> source /tmp/source1.sh
$> 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)
(bash) $BASH_SOURCE (/tmp/source1.sh) readlink -e $BASH_SOURCE (/tmp/source1.sh)




来自不同相对路径的/ source/tmp/a


关于/var


在所有情况下,如果脚本添加了命令

echo '
$> bash /tmp/source1.sh
(bash)
'"(
q4312078q (/tmp/source1.sh)
$BASH_SOURCE (/tmp/source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)
)"


然后source脚本始终打印

q4312078q

但是,如果脚本运行,例如

q4312078q

,那么/tmp/source1.sh将是字符串值q4312079q。

q4312078q

#10 楼

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

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


#11 楼

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

NB:依赖于GNU coreutils程序包中的现代realpath(1)实用程序

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=q4312078q
                ;;
    esac
    echo $(realpath $result)
}


#12 楼

如果您需要包含脚本的目录的绝对路径,则可以使用以下代码段:
BASE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]:-
SCRIPT_PATH="${BASH_SOURCE[0]:-q4312078q}"
}" )" >/dev/null 2>&1 && pwd )"

如果只需要脚本的相对路径:
q4312078q
这些代码段应在不同的bash版本和zsh上工作。

#13 楼

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


这可能不适用于符号链接或源文件,但适用于普通文件。
作为参考。 @kenorb
没有目录名,读取链接,BASH_SOURCE。

评论


问题中的解释是,$ 0会为您提供有关当前正在运行的脚本的信息,而不是源脚本。

–斯科特
19年6月9日在4:10

#14 楼

实际上,“目录名$ 0”将为您提供脚本的路径,但您必须对其进行一些解释:

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


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

评论


这是源脚本,不是已执行的脚本。 $ 0仅包含用于交互式shell的“ bash”,这就是所有源脚本所看到的。

–卡斯卡贝尔
2010-12-08 16:32