在Shell脚本中,如何以跨平台的方式查找shell可执行文件所在的目录?

5
根据 POSIX 标准:http://pubs.opengroup.org/onlinepubs/9699919799/utilities/sh.html,有一些情况并不明显。例如:
If the file is not in the current working directory,
the implementation may perform a search for an executable
file using the value of PATH, as described in Command Search and Execution.

我的Bash 4.x没有遵循这个可选规则(由于安全问题??),所以我无法测试它在实际生活中的表现...

有什么平台无关的方法可以在shell脚本中找到shell可执行文件的目录?

PS。同样,dirname $0的情况失败了:

#!/bin/sh
echo $0
dirname $0

当你:

$ sh runme.sh
runme.sh
.

所以您需要类似以下的东西:
CMDPATH=`cd $(dirname $0); echo $PWD`

为了使代码仅依赖于内置的 shell 功能,我对代码进行了重写:
PREVPWD=$PWD
cd ${0%${0##*/}}.
CMDPATH=$PWD
cd $PREVPWD

这看起来很丑,但不需要分叉任何可执行文件...

不,.表示它可以无缝运行:这是一个有效的相对路径名。如果在其前面添加$PWD,则变成一个有效的非规范化绝对路径名:/home/me/. 更复杂但仍然正确的例子:/home/me/project1/../../../opt/freeware/bin 你真正的问题似乎是“如何将路径名转换为绝对路径并处于规范化形式?” - kubanczyk
@kubanczyk 大多数问题来自 POSIX sh$0 的定义。请参阅标题和正文。 - gavenkoa
你为什么认为你的 bash 没有执行 PATH 查找?"may" 意味着该命令可能是 shell 内置或函数。请阅读你引用链接中的解决过程。 - chepner
@chepner 因为我的测试结果表明:cd /tmp; sh ls - 失败并显示 sh: 0: Can't open ls... - gavenkoa
1
@chepner: 尝试 cd /tmp; sh -c 'ls' - jackr
显示剩余2条评论
2个回答

5

编辑3:

虽然还不是严格的POSIX,但realpath自2012年起就是GNU核心应用程序。充分披露:在我注意到它后从未听说过它在info coreutils TOC中,并立即想到了这个问题,但使用以下函数所示的方法应该可靠(很快就会成为POSIX标准?),而且希望能够高效地提供其调用者绝对来源的$0

% _abs_0() { 
> o1="${1%%/*}"; ${o1:="${1}"}; ${o1:=`realpath -s "${1}"`}; eval "$1=\${o1}"; 
> }  
% _abs_0 ${abs0:="${0}"} ; printf %s\\n "${abs0}"
/no/more/dots/in/your/path2.sh

编辑4: 值得注意的是,此解决方案使用POSIX参数扩展来首先检查路径是否需要扩展和解析,然后再尝试进行扩展和解析。这应该能够以我能想象到的最高效的方式返回一个绝对源$0通过一个信使变量(值得注意的是,-s将保留符号链接),无论路径是否已经是绝对路径。

编辑2:

现在我相信我更好地理解了你的问题,不幸的是,这实际上使下面大部分内容都不相关了。

(小修改:在找到文档中的realpath之前,我至少将我的版本简化为不依赖时间字段,但是,公平警告,在测试一些之后,我不太确信ps在其命令路径扩展能力方面完全可靠)

另一方面,你可以这样做:

ps ww -fp $$ | grep -Eo '/[^:]*'"${0#*/}"

eval "abs0=${`ps ww -fp $$ | grep -Eo ' /'`#?}"

我需要修复它,使其更好地与字段配合使用,而不是只期望时间字段出现在进程路径之前,并依靠其包含的冒号作为参考,特别是因为这将无法处理您进程路径中的冒号,但我认为这是微不足道的并且很快就会发生。我相信该功能在其他方面符合POSIX标准。我认为仅参数扩展就可以做到必要的事情。 不太相关(或不正确): 这应该适用于符合POSIX指南的每种情况:
echo ${0%/*}

编辑:

我要承认,至少乍一看,我并不完全理解您描述的问题。显然,在您的问题中,您展示了一些关于通过参数扩展进行变量字符串操作的 POSIX 标准的熟悉度(即使您的特定实现在一瞥之间似乎有点紧张),所以我可能在我的问题解释中错过了一些重要信息,也许,至少在目前的形式下,这不是您寻求的答案。

我之前发布过有关内联变量空/设置测试的参数扩展,您可以在“Portable Way to Check Emptiness of a Shell Variable”问题中查看,这可能对您有用。我之所以提到这一点,主要是因为我的答案很大程度上是从 POSIX 参数扩展指南中复制/粘贴的,包括一个锚定链接,指向该主题的指南覆盖范围,并且包括一些来自经典文档和我自己构建的可能不太专业的示例。

然而,我自由地承认,虽然我还没有完全理解您的问题,但我不认为您会在那里找到具体的答案。相反,我怀疑您可能已经忘记了(就像我偶尔一样),即 POSIX 字符串操作中的 # 和 % 运算符用于指定要删除的字符串部分,而不是您希望保留的部分,这可能更直观。我的意思是,您以这种方式搜索的任何字符串片段都旨在从输出中消失,然后仅剩下您指定的搜索字符串被删除后原始字符串的剩余部分。

因此,以下是一些概述:

单个运算符将仅尽可能少地删除满足您搜索的内容,但是当双重运算符出现时,搜索将以贪婪形式进行,并且将尽可能多地删除原始字符串的内容。

除此之外,您只需要了解一些基本的正则表达式,并记住 # 从左侧开始搜索您要删除的字符串,并向右扫描,而 % 则从右侧开始搜索并向左扫描。

## short example before better learning if I'm on the right track
## demonstrating path manipulation with '#' and '%'
% _path_one='/one/two/three/four.five'
% _path_two='./four.five'
## short searching from the right with our wildcard to the right 
## side of a single character removes everything to the right of 
## of the specified character and the character itself
## this is a very simple means of stripping extensions and paths
% echo ${_path_one%.*} ${_path_one%/*}
/one/two/three/four   /one/two/three
## long searching from the left with the wildcard to the left of
## course produces opposite results 
% echo ${_path_one##*.} ${_path_one##*/}
five    four.five

## will soon come back to show more probably

+1 对于信使变量和疯狂的参数扩展。是否有一种非realpath解决方案,可以与相对符号链接一起使用? - Quantum7
@Quantum7 - 实际上,这很糟糕。这个 好多了。是的,它可以做所有那些事情。 - mikeserv

2

我相信你可以使用readlink:来实现。

scriptPath=$(readlink -f -- "$0")
scriptDirectory=${scriptPath%/*}

啊,我明白了。我在我的OSX上使用了readlink命令,以为它在任何地方都可用。 - anubhava

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接