如何在POSIX sh中获取脚本目录?
我在我的bash脚本中有以下代码。 现在我想在POSIX sh中使用它。 那么如何转换呢? 谢谢。
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )"
$BASH_SOURCE
的POSIX-shell( sh
)副本是$0
。 请参阅底部获取背景信息
警告 :最重要的区别是, 如果您的脚本正在源代码中(通过.
加载到当前shell) , 下面的代码片段将无法正常工作。 下面进一步解释
请注意,我在下面的代码片段中将DIR
更改为dir
,因为最好不要使用全大写变量名称,以避免与环境变量和特殊shell变量发生冲突。
在原始命令中, CDPATH=
prefix取代> /dev/null
: $CDPATH
被设置为一个空字符串,以确保cd
从不回应任何东西。
在最简单的情况下,这会做( 相当于OP的命令 ):
dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
如果您还想将生成的目录路径解析为其最终目标,以防目录和/或其组件是符号链接 ,请将-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
,而不是脚本本身。
要查找脚本自己的真正的原始目录,必须检查脚本的路径以查看它是否是符号链接,如果是,请遵循(链)符号链接到最终目标文件,然后从中提取目录路径目标文件的规范路径。
GNU的readlink -f
(更好: readlink -e
)可以为你做,但readlink
不是POSIX实用程序。
虽然包括OSX在内的BSD平台也有一个readlink
实用程序,但在OSX上它不支持-f
的功能。 也就是说,要显示如果readlink -f
可用,任务变得readlink -f
简单: dir=$(dirname "$(readlink -f -- "$0")")
。
实际上, 没有用于解析文件符号链接的POSIX实用程序 。 有办法可以解决这个问题,但它们很麻烦,而且不完全健壮:
以下符合POSIX标准的shell函数实现了GNU的readlink -e
它是一个相当健壮的解决方案 , 仅在两种罕见边缘情况下失败 :
->
(也很少见) 有了这个名为rreadlink
函数, 下面的函数确定了脚本的原始路径 :
dir=$(dirname -- "$(rreadlink "$0")")
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 '%sn' "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 '%sn' "${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 '%sn' "$(command dirname -- "${targetDir}")"
else
command printf '%sn' "${targetDir%/}/$fname"
fi
)
为了健壮和可预测,该函数使用command
来确保只调用shell内建函数或外部实用程序(忽略以别名和函数形式的重载)。
它已在最近版本的以下shell中进行了测试: bash
, dash
, ksh
和zsh
。
如何处理源调用:
tl; dr :
仅使用POSIX功能:
zsh
中,但通常不会像sh
)。 $0
与shell可执行文件名称/路径(在zsh
除外,其中$0
是真正的当前脚本路径)。 相比之下(除了zsh
),源自另一个本身直接调用的脚本的脚本包含该脚本的$0
路径。 bash
, ksh
和zsh
具有非标准功能 ,即使在源方案中也可以确定实际的脚本路径,还可以检测脚本是否正在源代码中; 例如,在bash
, $BASH_SOURCE
总是包含正在运行的脚本的路径,无论它是否来源,并且可以使用[[ $0 != "$BASH_SOURCE" ]]
来测试脚本是否来源。 为了说明为什么不能这样做 ,让我们分析一下Walter A的答案:
# 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
将简单地返回.
(因为这是dirname
在给定没有路径组件的情况下总是会执行的操作)。 command -v -- "$0"
然后通过$PATH
查找( /bin/sh
)返回该shell的绝对路径。 但是,请注意,某些平台(例如OSX)上的登录shell的文件名前缀为-
in $0
( -sh
),在这种情况下, command -v -- "$0"
不能按预期工作(返回一个空字符串)。 command -v -- "$0"
在两个非源代码的情况下可能会出错,这些情况下可直接调用shell可执行文件sh
,并将脚本作为参数: command -v -- "$0"
可能会返回一个空字符串,具体取决于特定shell在给定系统上作为sh
作用: bash
, ksh
和zsh
返回一个空字符串; 只有dash
回应$0
POSIX规范。 for
command
并没有明确地说出command -v
是否应用于文件系统路径时,应该只返回可执行文件 - 这就是bash
, ksh
和zsh
所做的 - 但是您可以争辩说它是由command
目的所暗示的; 奇怪的是, dash
通常是最符合标准的POSIX公民,这里偏离了标准。 相比之下, ksh
是这里唯一的模型公民,因为它是唯一一个仅报告可执行文件并按照规范要求以绝对路径(尽管不是标准化)报告路径的公民。 $PATH
,并且调用使用其单纯的文件名(例如sh myScript
),则command -v -- "$0"
也将返回空字符串,除了dash
。 $0
不包含这些信息(除了zsh
,通常不会像sh
) - 这个问题没有好的解决方案。 $0
: [ "$0" = "sh" ] || [ "$0" = "-sh" ] || [ "$0" = "/bin/sh" ]
[ "$0" = "sh" ] || [ "$0" = "-sh" ] || [ "$0" = "/bin/sh" ]
$0
只包含脚本的路径。 command -v -- "$0"
在源代码方案中的有用性有限,以及它打破了两个非源方案的事实, 我的投票是不使用它 ,这留下了我们: $dir
最终会包含.
如果外壳可执行文件被调用作为一个单纯的文件名(在应用dirname
到只有文件名总是返回.
),或外壳可执行文件的目录路径,否则。 .
不能可靠地从当前目录中的非源调用区分开来。 $0
包含该脚本的路径,并且源代码的脚本无法判断是否属于这种情况。 背景信息:
POSIX在这里定义了$0
相对于shell脚本的行为 。
实质上, $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
都表现出这种行为。
相比之下,POSIX不强制的价值$0
采购的脚本 (使用特殊的内置工具时 .
(“点”)),所以你不能依赖它 ,并在实践中, 行为跨越炮弹有所不同。
$0
。 bash
, dash
和ksh
在采购脚本时保持$0
不变,这意味着$0
包含调用者的$0
值,或者更准确地说,调用链中最近一次调用者的$0
值没有被发现; 因此, $0
可能指向shell的可执行文件或指向源自当前文件的另一个(直接调用的)脚本的路径。 zsh
实际上报告当前脚本的路径为$0
。 相反, $0
不会提供脚本是否来源的指示。 $0
与当前脚本路径之间的关系。 bash
, ksh
和zsh
都提供了自己的方式来获取正在运行的脚本的路径,即使它正在被获取。 为了完整起见: 在其他情况下$0
的价值 :
$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
都表现得那样。 $0
是shell父进程传递的第一个参数的值 - 通常是shell的名称或路径(例如sh
或/bin/sh
); 这包括: $0
之前预先挂载-
以便向shell发出它是_login shell的信号; 因此,默认情况下, $0
在OSX的交互式shell中报告-bash
,而不是bash
。 sh < myScript
) bash
, dash
, ksh
和zsh
都表现得那样。 @City回应说
DIR=$( cd -P -- "$(dirname -- "$(command -v -- "$0")")" && pwd -P )
作品。 我也使用它。
我在https://stackoverflow.com/questions/760110/can-i-get-the-absolute-path-to-the-cu rrent-script-in-kornshell中找到了该命令。