我的bash脚本中有以下代码。现在我想在POSIX sh中使用它。如何转换?
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )"
最佳答案
sh
的POSIX-shell($BASH_SOURCE
)对应项是$0
。请参阅底部的背景信息
警告:关键区别在于(如果您的脚本源于.
(使用DIR
加载到当前 shell 中)),则下面的代码段将无法正常工作。 在下面的解释
请注意,在下面的代码段中,我已将dir
更改为CDPATH=
,因为它是better not to use all-uppercase variable names,以避免与环境变量和特殊的shell变量发生冲突。
原始命令中的> /dev/null
前缀代替了$CDPATH
:cd
设置为空字符串,以确保-P
从不回显任何内容。
在最简单的情况下,可以这样做(相当于OP命令的):dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
如果您还想将生成的目录路径解析为其最终目标,以防目录和/或其组件是符号链接(symbolic link),请将pwd
添加到foo
命令中:dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)
警告:这是,而不是,与查找脚本自己的原始来源的真实目录相同:
假设您的脚本/usr/local/bin/foo
与$PATH
中的/foodir/bin/foo
符号链接(symbolic link),但其真实路径为/usr/local/bin
。
上面的代码仍会报告-P
,因为符号链接(symbolic link)解析(/usr/local/bin
)应用于目录readlink -f
,而不是脚本本身。
要找到脚本自己的原始来源的真实目录,您必须检查脚本到的路径,以查看它是否是symlink ,如果是,请遵循(链)符号链接(symbolic link)到最终目标文件,然后解压缩目标文件规范路径中的目录路径。
GNU的readlink -e
(更好:readlink
)可以为您做到这一点,但是readlink
不是POSIX实用程序。
虽然BSD平台(包括macOS)也具有-f
实用程序,但在macOS上,它不支持readlink -f
的功能。就是说,要显示,如果dir=$(dirname "$(readlink -f -- "$0")")
可用,任务将变得多么简单:readlink -e
。
实际上,没有没有POSIX实用程序来解决文件符号链接(symbolic link)。
有一些方法可以解决此问题,但是它们很麻烦并且不够完善:
遵循POSIX的以下符合POSIX的 shell 函数实现了GNU的->
所做的,并且是一种相当健壮的解决方案,仅在两种罕见的极端情况下失败:
带有嵌入式换行符的rreadlink
的
使用名为readlink
的此函数定义为,以下命令将确定脚本的原始目录的真实目录路径:dir=$(dirname -- "$(rreadlink "$0")")
注意:如果您愿意假设存在一个(非POSIX)rreadlink()
实用程序-它将覆盖macOS,FreeBSD和Linux-类似但更简单的解决方案可以在this answer中找到相关问题。command
源代码-脚本中调用之前的位置:
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
)
为了健壮和可预测,该函数使用bash
来确保仅调用shell内置函数或外部实用程序(忽略别名和函数形式的重载)。
已经在以下Shell的最新版本中对进行了测试:dash
,ksh
,zsh
,zsh
。
如何处理源调用:
tl;博士:
仅使用POSIX功能:sh
除外,但通常不充当$0
)。 zsh
与 shell 进行比较,来检测是否仅脚本的来源可执行文件名称/路径($0
除外,其中zsh
实际上是当前脚本的路径)。相反,($0
除外),从另一个本身直接被调用的脚本派生的脚本在bash
中包含该脚本的路径。 ksh
,zsh
和bash
具有非标准功能,即使在来源场景中也可以确定实际的脚本路径,并且还可以检测脚本是否在来源。例如,在$BASH_SOURCE
中,[[ $0 != "$BASH_SOURCE" ]]
始终包含正在运行的脚本的路径,无论它是否是源代码,并且-P
可用于测试脚本是否是源代码。
为了显示为什么无法做到这一点,让我们分析Walter A's answer的命令: # NOT recommended - see discussion below.
DIR=$( cd -P -- "$(dirname -- "$(command -v -- "$0")")" && pwd -P )
pwd
是多余的-与cd
一起使用就足够了。 $CDPATH
,则该命令缺少command -v -- "$0"
的潜在stdout输出的沉默。)command -v -- "$0"
$0
旨在解决另一种情况:如果脚本是从交互式 shell 程序中获取的,则sh
通常仅包含 shell 程序可执行文件的文件名(dirname
),在这种情况下,.
只会返回dirname
(因为command -v -- "$0"
始终会执行此操作)给定不带路径成分的参数时)。
然后,$PATH
通过/bin/sh
查找(-
)返回该 shell 的绝对路径。但是请注意,在某些平台上(例如OSX),登录shell的文件名带有$0
(-sh
)中的command -v -- "$0"
前缀,在这种情况下command -v -- "$0"
无法按预期工作(返回空字符串)。 sh
在两种非源场景中可能会出现异常,在这种情况下,直接以脚本作为参数调用shell可执行文件command -v -- "$0"
:sh
可能返回空字符串,具体取决于给定系统上特定的shell充当bash
:ksh
,zsh
和dash
返回空字符串;仅$0
回显command
POSIX spec. for 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
command -v -- "$0"
然后仅包含源脚本的路径。 $dir
在来源场景中的用处有限,并且它破坏了两个非来源场景的事实,因此我的投票是不使用它,这给我们留下了:.
要么包含dirname
,要么将 shell 程序可执行文件作为纯文件名调用(将.
应用于纯文件名始终返回.
),或者shell可执行文件的目录路径,否则。无法可靠地将$0
与当前目录中的非源调用区分开。 $0
包含该脚本的路径,并且正在获取的脚本无法判断是否是这种情况。
背景资料:
POSIX 定义$0
相对于shell脚本 here的行为。
本质上,$0
应该反射(reflect)指定的脚本文件的路径,这意味着:$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
和$0
都表现出此行为。
相比之下, POSIX在采购脚本时(使用特殊的内置实用程序.
(“dot”))不要求$0
的值,因此您不能依赖它,实际上,的行为有所不同跨壳。 bash
。dash
,ksh
和$0
保持$0
不变,这意味着$0
包含调用者的$0
值,或更准确地说,包含调用链中最近未提供自身的调用者的$0
值;因此,zsh
可能指向 shell 程序的可执行文件,也可能指向源于当前脚本的另一个(直接调用)脚本的路径。 $0
实际上在$0
中报告了当前脚本的路径。相反,$0
将不提供有关脚本是否正在来源的指示。 bash
与当前脚本路径之间的关系是什么。 ksh
,zsh
和$0
都提供了自己的方式来获取运行脚本的路径,即使是在获取源脚本的情况下也是如此。
为了完整起见:在其他上下文中$0
的值:bash
保持不变;因此,无论它在函数外部具有什么值,它也将在内部具有。dash
,ksh
和zsh
确实具有这种行为。 -c
是唯一的反对者,它报告函数的名称。 $0
选项sh -c 'echo \$0: $0 \$1: $1' foo one # -> '$0: foo $1: one'
的第一个操作数(非选项参数);例如。:bash
dash
,ksh
,zsh
和$0
都采用这种方式。 sh
是 shell 的父进程传递了的第一个参数的值-通常是 shell 的名称或路径(例如/bin/sh
或-
);这包括:$0
放在shell名称之前添加$0
,以便向shell发出信号,表明它是_login shell;因此,默认情况下,-bash
在OSX的交互式shell中报告bash
而不是sh < myScript
。 bash
)将脚本文件管道传输到 shell dash
,ksh
,zsh
和ojit_code都采用这种方式。
关于bash - 如何在POSIX sh中获取脚本目录?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29832037/