我的bash脚本中有以下代码。 现在我想在POSIX中使用它。 那么如何转换呢? 谢谢。
1
| DIR="$( cd"$( dirname"${BASH_SOURCE[0]}" )"> /dev/null && pwd )" |
-
问题不在于数组。问题是BASH_SOURCE这是一个bash-ism。这个问题还有其他答案,有mywiki.wooledge.org/BashFAQ/028
-
你的问题与数组的关系不如BASH_SOURCE变量,显然只能在bash中使用。
-
@EtanReisner,谢谢。我已经更新了我的问题。你知道posix sh中BASH_SOURCE的替换是什么吗?谢谢
-
试试DIR=$( cd -P --"$(dirname --"$(command -v --"$0")")" && pwd -P ),我在http://stackoverflow.com/questions/760110/can-i-get-the-absolute-path-to-the-current-script-in-kornshell找到的。
-
我想知道command用法应该在那里做什么。对于PATH以外的任何事情来说,这似乎都是错误的。
-
@WalterA,谢谢。有用。
-
@EtanReisner:只要command -v的参数是可执行文件的路径(而不仅仅是文件名,在这种情况下匹配将限于$PATH),command -v将起作用。然而,它只是回应那条道路,虽然它似乎没有伤害,但我不知道为什么它在那里。
-
@ mklement0你对command的行为有参考吗?我看到规范说"command_names包括一个字符......应该写成绝对路径名",但这并没有说明可执行部分。
-
@EtanReisner:我认为POSIX规范中的这段话暗示了这一点。 of command(强调我的):"当使用-v或-v选项时,命令实用程序应提供有关shell如何解释命令名称的信息。"
-
@ mklement0我去找了一个具体的command_name定义,但找不到一个(我没有找到命令名),但也许就是这样。
-
可能重复的Bash脚本:将当前工作目录设置为脚本目录
-
另请参阅:bash脚本获取自身完整路径的可靠方法?
-
并且:Bash脚本可以告诉它存储在哪个目录中吗?
-
@kenorb:请注意,这个问题明确是关于POSIX shell(sh),而不是bash,这是你链接到的问题被标记的;即使那里的答案恰好包含符合POSIX标准的解决方案,也不会这样讨论。
-
@EtanReisner:command_name被定义为"实用程序的名称或特殊的内置实用程序" - 我猜任何可执行文件都有资格作为实用程序。该页面还指出,"command_names包括一个<斜杠>字符......应写为绝对路径名" - 注意绝对值,只有ksh实现这种方式 - dash,bash和zsh简单地回显指定的路径。作为额外的好奇心,dash也回应了可执行目录。我想我终于找到了command -v --"$0"的目的 - 它是 - 有点 - 涵盖了源代码调用;看到我更新的答案。
-
@EtanReisner:附录:dash实际上回应传递给command -v的任何现有路径 - 甚至目录 - 可执行不是必需的 - 与bash,ksh和zsh不同。
-
CURRENT_SCRIPT_DIRECTORY="$(basename"`pwd"$(dirname -- readlink $0)"`")"
$BASH_SOURCE的POSIX-shell(sh)对应是$0。请参阅底部的背景信息
警告:关键的区别在于,如果您的脚本被采购(使用.加载到当前shell中),下面的代码段将无法正常工作。进一步解释如下
请注意,我在下面的代码段中将DIR更改为DIR,因为最好不要使用全大写变量名,以避免与环境变量和特殊shell变量发生冲突。
CDPATH=前缀取代原始命令中的> /dev/null:$CDPATH设置为空字符串,以确保cd永远不会回显任何内容。
在最简单的情况下,这将做(相当于OP的命令):
1
| dir=$(CDPATH= cd --"$(dirname --"$0")" && pwd) |
如果您还想将结果目录路径解析为其最终目标,以防目录和/或其组件是符号链接,请将-P添加到pwd命令:
1
| 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实用程序。
虽然BSD平台(包括OSX)也有readlink实用程序,但在OSX上它不支持-f的功能。也就是说,要显示如果readlink -f可用,任务变得多么简单:dir=$(dirname"$(readlink -f --"$0")")。
实际上,没有用于解析文件符号链接的POSIX实用程序。
有办法解决这个问题,但它们很麻烦且不够完善:
以下,符合POSIX标准的shell函数实现了GNU的readlink -e所做的功能,并且是一个相当强大的解决方案,只在两种罕见的边缘情况下失败:
嵌入换行符的路径(非常罕见)
包含文字字符串->的文件名(也很少见)
使用名为rreadlink的此函数定义,以下内容确定脚本的真实目录路径:
1
| dir=$(dirname --"$(rreadlink"$0")") |
rreadlink()源代码 - 在脚本中调用之前放置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| 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
'"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
'"${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
'"$(command dirname --"${targetDir}")"
else
command printf '%s
'"${targetDir%/}/$fname"
fi
) |
为了具有可靠性和可预测性,该函数使用command来确保仅调用shell内置函数或外部实用程序(忽略别名和函数形式的重载)。
它已在以下shell的最新版本中进行了测试:bash,dash,ksh,zsh。
如何处理源调用:
TL;博士:
仅使用POSIX功能:
您无法在源调用中确定脚本的路径(zsh除外,但zsh通常不会作为sh)。
如果您的脚本是由shell直接提供的(例如在shell配置文件/初始化文件中;可能通过一连串的源代码),通过将$0与shell可执行文件进行比较,您可以检测脚本是否仅来源名称/路径(zsh除外,其中,如上所述$0确实是当前脚本的路径)。相比之下(zsh除外),源自另一个本身直接调用的脚本的脚本在$0中包含该脚本的路径。
为了解决这些问题,bash,ksh和zsh具有非标准功能,即使在源方案中也可以确定实际的脚本路径,还可以检测脚本是否来源;例如,在bash中,$BASH_SOURCE始终包含正在运行的脚本的路径,无论它是否来源,并且[[ $0 !="$BASH_SOURCE" ]]可用于测试脚本是否来源。
为了说明为什么不能这样做,让我们分析Walter A答案中的命令:
1 2
| # NOT recommended - see discussion below.
DIR=$( cd -P --"$(dirname --"$(command -v --"$0")")" && pwd -P ) |
(两个旁边:
使用-P两次是多余的 - 将它与pwd一起使用就足够了。
如果$CDPATH碰巧被设置,则该命令缺少cd的潜在stdout输出的静默。)
command -v --"$0"
command -v --"$0"旨在涵盖一个额外的场景:如果脚本来自交互式shell,$0通常只包含shell可执行文件的文件名(sh),在这种情况下,dirname将只返回< x5>(因为当给出没有路径组件的参数时,dirname总是会这样做)。
command -v --"$0"然后通过$PATH查找(/bin/sh)返回该shell的绝对路径。但请注意,某些平台(例如OSX)上的登录shell在$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) - 没有好的解决方案这个问题。
在这种情况下返回shell可执行文件的目录路径是有限的用途 - 毕竟,它不是脚本的目录 - 除非稍后在测试中使用该路径来确定脚本是否来源。
更可靠的方法是直接测试$0:["$0" ="sh" ] || ["$0" ="-sh" ] || ["$0" ="/bin/sh" ]
但是,如果脚本来自另一个脚本(本身直接调用),即使这样也行不通,因为$0只是包含了源脚本的路径。
鉴于command -v --"$0"在源场景中的有用性有限,以及它打破了两个非源场景的事实,我的投票是不使用它,这让我们:
涵盖所有非源方案。
在源调用中,您无法确定脚本的路径,并且在有限的情况下,您可以检测是否正在进行采购:
当直接由shell(例如来自shell配置文件/初始化文件)获取时,$dir最终包含.,如果shell可执行文件仅作为文件名调用(将dirname应用于纯文件名始终返回.),否则为shell可执行文件的目录路径。无法可靠地将.与当前目录中的非源调用区分开来。
当源自另一个脚本(本身也不是源代码)时,$0包含该脚本的路径,而源代码的脚本无法判断是否是这种情况。
背景资料:
POSIX在这里定义了关于shell脚本的$0的行为。
实质上,$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的值(使用特殊的内置实用程序.("dot")),因此您不能依赖它,并且在实践中,行为会有所不同炮弹。
因此,当您的脚本被采购并期望标准化行为时,您不能盲目地使用$0。
实际上,bash,dash和ksh在获取脚本时保持$0不变,这意味着$0包含调用者的$0值,或者更确切地说,$0值包含呼叫链中最近的呼叫者本身并未采购;因此,$0可以指向shell的可执行文件,也可以指向源于当前shell的另一个(直接调用的)脚本的路径。
相比之下,作为唯一的反对者,zsh实际上会在$0中报告当前脚本的路径。相反,$0将不提供脚本是否来源的指示。
简而言之:仅使用POSIX功能,既不能可靠地判断手头的脚本是否来源,也不能说明手头的脚本是什么,也不知道$0与当前脚本路径的关系是什么。
如果确实需要处理这种情况,则必须确定手头的特定shell并访问其特定的非标准功能:
bash,ksh和zsh都提供了获取正在运行的脚本路径的自己的方法,即使它是来源的。
为了完整起见:$0的值在其他上下文中:
在shell函数中,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);这包括:
交互式shell
警告:一些平台,特别是OSX,在创建交互式shell时始终创建登录shell,并在将-放入$0之前将-添加到shell名称之前,以便向shell发出信号,告知它是_login shell;因此,默认情况下,$0在OSX上的交互式shell中报告-bash,而不是bash。
从stdin读取命令的shell
这也适用于通过stdin将脚本文件传递给shell(例如,sh < myScript)
bash,dash,ksh和zsh都表现得那样。
好。
-
这个是Tag wiki的候选者。好答案。
-
谢谢,@ DavidC.Rankin。
-
吓人!可惜我不能多次投票!
-
这是一个详细的答案......哇。
-
美丽的答案!
1 2 3 4
| if OLDPWD=/dev/fd/0 \
cd - && ls -lLidFH ?
then cd . <8
fi </proc/self/fd 8<. 9<$0 |
那里。这应该使您能够通过一些魔术链接更改directpry作为文件描述符。
设置$OLDPWD pre- cd导出一个更改目录的持续时间的值(注意:cd可能对hash表有残留影响,但唯一的sh我知道实际是男性任何好用的都是kevin ahlmquists - 并且因为herbert xu - dash,也许是一些bsd的东西,但我知道什么?)但是由于改变而没有带来任何cd导出。
因此,$OLDPWD实际上并没有改变,如果它有任何价值,那么它仍然保持不变。作为第一个cd的结果,$PWD被更改,并且值变为/dev/fd/0,其指向/proc/self/fd,其中我们的进程的文件描述符列表应该在.中,以包含$0 >在./2上。
所以我们做了ls ... ?,看看我们可以得到的所有精彩信息,我们从哪里来。
好极了!
@City回答说
1
| 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找到了这个命令。
-
没有坏处,但通常在评论中引用其他堆栈帖子,而不是作为答案发布。提供链接的SO格式是[链接标题](url)。 (您可以通过用双星号包围链接标题来包括普通粗体)例如。我可以在KornShell中获取当前脚本的绝对路径吗?
-
@David感谢格式,我确实先发表评论。我的回复是我的评论有效,所以我想提供一个结束这个问题的可能性。其他人可能会认为City现在仍在寻找答案。你能告诉我应该怎么做吗?
-
即使评论有效,也应该留在评论中作为参考。为什么?因为答案已经在SO上,并且SO上的一个巨大问题是在不同问题下分散在同一答案中的可忽略不计的版本。目标是在一个问题下获得所有相关答案,以便所有人都能轻松找到/引用它们。 (我们永远不会100%到达那里,但是当你在另一个问题下找到答案时,请不要再复制它)
-
@ DavidC.Rankin:请注意,这个问题明确是关于POSIX shell(sh),而这里链接的问题是关于ksh,顶部注释中链接的问题是bash问题;虽然这些问题的一些答案恰好包含了符合POSIX标准的解决方案,但它们并没有这样讨论,尽管这个问题是密切相关的,但它是独特的,具有普遍意义,并且值得拥有自己的,规范的回答这里。也许Walter可以复制ksh标记的解决方案并解释为什么它适用于所有符合POSIX标准的shell。