$BASH_SOURCE 的 POSIX-shell (sh) 对应物是 $0。 背景信息见底部
警告:关键的区别在于如果您的脚本是来源(使用. 加载到当前 shell ),下面的 sn-ps 将无法正常工作。 下面有进一步的解释
注意我在下面的sn-ps中把DIR改成了dir,因为是better not to use all-uppercase variable names,以免和环境变量和特殊的shell变量发生冲突。
CDPATH= 前缀取代了原始命令中的 > /dev/null:$CDPATH 设置为空字符串,以确保 cd 永远不会回显任何内容。
在最简单的情况下,这样做(OP 的命令的等效项):
dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
如果您还想将 resulting 目录路径解析为最终目标,以防目录和/或其组件是符号链接,将-P 添加到pwd 命令:
dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)
警告:这不与查找脚本自己的真实原始目录相同:
假设您的脚本foo 符号链接到$PATH 中的/usr/local/bin/foo,但它的真实路径是/foodir/bin/foo。
以上仍会报告/usr/local/bin,因为符号链接解析(-P)应用于目录,/usr/local/bin,而不是脚本本身。
要找到脚本自己的真实来源目录,您必须检查 脚本的 路径以查看它是否是 符号链接 em> ,如果是这样,按照符号链接(链)指向最终目标文件,然后从 target 文件的规范路径中提取目录路径。
GNU 的 readlink -f(更好的是:readlink -e)可以为您做到这一点,但 readlink 不是 POSIX 实用程序。
虽然包括 macOS 在内的 BSD 平台也有一个 readlink 实用程序, 在 macOS 上它不支持 -f 的功能。也就是说,为了显示任务变得多么简单如果readlink -f 可用:
dir=$(dirname "$(readlink -f -- "$0")")。
事实上,没有 POSIX 实用程序可用于解析文件 符号链接。
有一些方法可以解决这个问题,但它们很麻烦并且不完全健壮:
以下 POSIX 兼容的 shell 函数实现了 GNU 的 readlink -e 所做的,并且是一个相当稳健的解决方案,仅在两种罕见的极端情况下失败:
- 嵌入换行符的路径(非常罕见)
- 包含文字字符串
-> 的文件名(也很少见)
使用这个函数,命名为rreadlink,定义后,以下内容确定了脚本真正的原始目录路径:
dir=$(dirname -- "$(rreadlink "$0")")
注意:如果您愿意假设存在(非 POSIX)readlink 实用程序 - 涵盖 macOS、FreeBSD 和 Linux - 可以使用类似但更简单的解决方案在this answer 中找到相关问题。
rreadlink() 源代码 - 在脚本中在调用它:
rreadlink() ( # Execute the function in a *subshell* to localize variables and the effect of `cd`.
target=$1 fname= targetDir= CDPATH=
# Try to make the execution environment as predictable as possible:
# All commands below are invoked via `command`, so we must make sure that `command`
# itself is not redefined as an alias or shell function.
# (Note that command is too inconsistent across shells, so we don't use it.)
# `command` is a *builtin* in bash, dash, ksh, zsh, and some platforms do not even have
# an external utility version of it (e.g, Ubuntu).
# `command` bypasses aliases and shell functions and also finds builtins
# in bash, dash, and ksh. In zsh, option POSIX_BUILTINS must be turned on for that
# to happen.
{ \unalias command; \unset -f command; } >/dev/null 2>&1
[ -n "$ZSH_VERSION" ] && options[POSIX_BUILTINS]=on # make zsh find *builtins* with `command` too.
while :; do # Resolve potential symlinks until the ultimate target is found.
[ -L "$target" ] || [ -e "$target" ] || { command printf '%s\n' "ERROR: '$target' does not exist." >&2; return 1; }
command cd "$(command dirname -- "$target")" # Change to target dir; necessary for correct resolution of target path.
fname=$(command basename -- "$target") # Extract filename.
[ "$fname" = '/' ] && fname='' # !! curiously, `basename /` returns '/'
if [ -L "$fname" ]; then
# Extract [next] target path, which may be defined
# *relative* to the symlink's own directory.
# Note: We parse `ls -l` output to find the symlink target
# which is the only POSIX-compliant, albeit somewhat fragile, way.
target=$(command ls -l "$fname")
target=${target#* -> }
continue # Resolve [next] symlink target.
fi
break # Ultimate target reached.
done
targetDir=$(command pwd -P) # Get canonical dir. path
# Output the ultimate target's canonical path.
# Note that we manually resolve paths ending in /. and /.. to make sure we have a normalized path.
if [ "$fname" = '.' ]; then
command printf '%s\n' "${targetDir%/}"
elif [ "$fname" = '..' ]; then
# Caveat: something like /var/.. will resolve to /private (assuming /var@ -> /private/var), i.e. the '..' is applied
# AFTER canonicalization.
command printf '%s\n' "$(command dirname -- "${targetDir}")"
else
command printf '%s\n' "${targetDir%/}/$fname"
fi
)
为了稳健和可预测,该函数使用 command 来确保仅调用 shell 内置函数或外部实用程序(忽略别名和函数形式的重载)。
它已在以下 shell 的最新版本中进行了测试:bash、dash、ksh、zsh。
如何处理来源调用:
tl;dr:
仅使用 POSIX 功能:
- 您无法在来源调用中确定脚本的路径(
zsh 除外,其中,但是,通常不充当sh)。
- 您可以检测是否只有当您的脚本直接由 shell 获取时,您的脚本才被获取(例如在shell 配置文件/初始化文件;可能通过 链 源),通过将
$0 与 shell 可执行文件名称/路径进行比较(zsh 除外,其中,如前所述,$0 是真正的当前脚本的路径)。相比之下(zsh 除外),源自另一个脚本 的脚本本身被直接调用,在@ 中包含那个 脚本的路径987654372@.
-
为了解决这些问题,
bash、ksh 和 zsh 具有非标准 功能,确实 允许确定实际的脚本路径,甚至在源场景中,还检测脚本是否被源;比如在bash中,$BASH_SOURCE总是包含了运行脚本的路径,是否被源码,[[ $0 != "$BASH_SOURCE" ]]可以用来测试脚本是否被源码。李>
为了说明为什么不能这样做,我们来分析来自Walter A's answer的命令:
# NOT recommended - see discussion below.
DIR=$( cd -P -- "$(dirname -- "$(command -v -- "$0")")" && pwd -P )
- (两个旁白:
- 使用
-P 两次是多余的 - 与 pwd 一起使用就足够了。
- 如果恰好设置了
$CDPATH,则该命令缺少 cd 的潜在标准输出输出的静默。)
-
command -v -- "$0"
-
command -v -- "$0" 旨在涵盖另一种情况:如果脚本是从 交互式 shell 获取的,$0 通常包含文件名 的 shell 可执行文件 (sh),在这种情况下,dirname 将简单地返回 .(因为当给定一个没有 path 组件的参数时,dirname 总是这样做)。
command -v -- "$0" 然后通过 $PATH 查找 (/bin/sh) 返回该 shell 的绝对路径。但是请注意,某些平台(例如 OSX)上的 login shell 在$0 (-sh) 中的文件名前缀为-,在这种情况下command -v -- "$0" 不起作用符合预期(返回一个空字符串)。
- 相反,
command -v -- "$0" 可能在两个非来源场景中出现异常行为,其中 shell 可执行文件 sh 被直接调用,以脚本作为参数:
- 如果脚本本身是不可可执行的:
command -v -- "$0" 可能会返回一个空字符串,这取决于在给定系统上充当sh 的特定shell:@ 987654402@、ksh、zsh 返回一个空字符串;只有dash 回显$0
POSIX spec. for command 没有明确说明command -v 在应用于文件系统路径时是否应该只返回可执行文件 - 这就是 bash、ksh 和 zsh 所做的 - 但您可以争辩说,这是由 command 的目的所暗示的;奇怪的是,dash,通常是最符合 POSIX 的公民,在这里偏离了标准。相比之下,ksh 是这里唯一的模型公民,因为它是唯一一个仅报告可执行文件并且使用绝对(尽管未标准化)路径报告它们的模型公民,按照规范的要求。
- 如果脚本是可执行的,但不在
$PATH 中,并且调用使用它的仅仅是文件名(例如,sh myScript),command -v -- "$0" 将也返回 空字符串,dash 除外。
- 鉴于脚本的目录无法在脚本被获取时确定 - 因为
$0 不包含该信息(zsh 除外,这通常不会充当sh) - 这个问题没有好的解决方案。
- 在这种情况下返回 shell 可执行文件的 目录路径的用处有限 - 毕竟,它不是 脚本的 目录 - 除非以后在测试 来确定是否脚本正在被获取。
- 更可靠的方法是直接测试
$0:[ "$0" = "sh" ] || [ "$0" = "-sh" ] || [ "$0" = "/bin/sh" ]
- 但是,如果脚本是从另一个脚本(它本身直接调用的)获取的,那么即使这样也不起作用,因为
$0只包含采购脚本的路径。
- 鉴于
command -v -- "$0" 在来源场景中的有限用处以及它破坏了两个非来源场景的事实,我的投票是不使用它,这就离开了我们与:
- 涵盖所有非来源的场景。
-
在 sourced 调用中,您无法确定脚本的路径,并且充其量,在有限的情况下,您可以检测是否采购正在发生:
- 当由 shell直接获取时(例如从 shell 配置文件/初始化文件),
$dir 最终会包含 .,如果 shell 可执行文件被调用为仅仅是一个文件名 (将dirname 应用于单纯的文件名总是 返回.),否则返回shell 可执行文件的目录路径。 . 无法可靠地区分来自当前目录的非源调用。
- 当来自另一个脚本(它本身没有被来源)时,
$0 包含 那个脚本的路径,并且被来源的脚本无法判断是否就是这样。
背景资料:
POSIX 定义了$0 相对于shell 脚本 here 的行为。
基本上,$0 应该反映脚本文件的路径指定,这意味着:
-
不要依赖包含绝对路径的
$0。
-
$0 包含绝对路径,仅当:
- 您明确指定绝对路径;例如。:
-
~/bin/myScript(假设脚本本身是可执行的)
sh ~/bin/myScript
- 您通过仅文件名调用一个可执行脚本,这要求它在
$PATH中既是可执行的和;系统在后台将myScript转换成绝对路径然后执行;例如。:
myScript # executes /home/jdoe/bin/myScript, for instance
-
在所有其他情况下,$0 将反映脚本路径指定:
- 当使用脚本显式调用
sh 时,这可以是仅仅是文件名(例如,sh myScript)或相对路径(例如,sh ./myScript )
-
直接调用可执行脚本时,这可以是相对路径(例如,
./myScript - 请注意仅仅是文件名只能在$PATH中找到脚本)。
在实践中,bash、dash、ksh 和 zsh 都表现出这种行为。
相比之下,当采购脚本(使用特殊的内置实用程序.(“点”) ),因此您不能依赖它,并且在实践中,shell 的行为会有所不同。
- 因此,您不能在获取脚本时盲目使用
$0 并期望标准化行为。
- 实际上,
bash、dash 和 ksh 在采购脚本时保留$0 未触及,这意味着$0 包含调用者的 @ 987654459@ 值,或者更准确地说,是调用链中最近调用者的$0 值,该调用者本身尚未被获取;因此,$0 可以指向 shell 的可执行文件或指向当前脚本的 another(直接调用)脚本的路径。
- 相比之下,
zsh,作为唯一的反对者,实际上确实在$0 中报告当前脚本的路径。相反,$0 不会提供有关脚本是否来自源的指示。
- 简而言之:仅使用 POSIX 功能,您既不能可靠地判断手头的脚本是否是来源,也不能可靠地判断手头的脚本的路径是什么,也不能确定
$0 与当前脚本路径的关系是。
- 如果您确实需要处理这种情况,您必须识别手头的特定外壳并访问其特定非标准功能:
-
bash、ksh 和 zsh 都提供了他们自己的方法来获取运行脚本的路径,即使它正在被获取。
为了完整起见:$0 在其他上下文中的值:
-
在外壳函数中,POSIX 要求
$0 保持不变;因此,无论它在函数外部具有什么值,它也将在内部具有。
- 在实践中,
bash、dash 和 ksh 的行为确实如此。
- 同样,
zsh 是唯一的反对者,并报告了函数的名称。
- 在启动时通过
-c 选项 接受命令字符串的shell 中,设置$0 的是第一个操作数(非选项参数);例如。:
sh -c 'echo \$0: $0 \$1: $1' foo one # -> '$0: foo $1: one'
-
bash、dash、ksh 和 zsh 都是这种行为。
-
否则,在不执行脚本文件的shell中,
$0是shell的的第一个参数的值>父进程已通过 - 通常是shell的名称或路径(例如sh或/bin/sh);这包括:
-
交互式外壳
-
警告:某些平台,尤其是 OSX,总是在创建交互式 shell 时创建 login shell,并在放置前将
- 添加到 shell 名称前在$0 中,以便向 shell 发出信号,表明它是一个 _login shell;因此,默认情况下,$0 在 OSX 上的交互式 shell 中报告 -bash,而不是 bash。
- 从 stdin 读取 命令的 shell
- 这也适用于通过标准输入将脚本文件传送到 shell(例如,
sh < myScript)
-
bash、dash、ksh 和 zsh 都是这种行为。