可靠地识别包含运行脚本的目录的路径

可靠地识别包含运行脚本的目录的路径

SO中有很多答案可以解决这个问题:

还有一些类似的问题在 Unix 和 Linux 中来源脚本。

尽管如此,其中一些是 Bash 特定的(它们依赖于$BASH_SOURCE)。他们中有一些如果目录名称包含换行符,则它们不起作用,并且如果涉及符号链接,其中一些将无法正常工作。

什么是可靠的在每个主要 shell 中获取包含正在运行/调用脚本的目录的路径的方法?拥有一个检查每种 shell 类型的解决方案就很好了。

答案1

我将对此进行第一阶段的尝试。希望其他人会有所改进。

在执行脚本之前,shell 将打开该文件的文件描述符。通常它被分配在 fd 255 处。无论如何,如果有一个 open fd,那么lsof就可以找到它。因此我们使用lsof -p $$并获取最高文件描述符的文件名。lsof并不适用于所有版本的 UNIX。 BSD 的 wiki 说有等效的fstat.似乎是在 Darwin (Mac OS) 上。与`-F

示例脚本:

#!/bin/sh

this_script_path=`lsof -p $$  | awk '/\/'${0##*/}'$/' | cut -c 55-`

显然,剪切非常依赖于 lsof 的特定格式。我们可以在版本 2 中缓解这个问题。顺便说一句:我的版本lsof会翻译不可打印的字符,这样即使路径名中的制表符也会转换为\t.

版本2。对于丑陋的 Perl 代码提前表示歉意。这次我们将使用-F选项来控制输出。我们-F fn将得到这样的输出:

p3834
fcwd
n/home/joe/test
frtd
n/
ftxt
n/bin/bash
fmem
n/lib64/ld-2.12.so
fmem
n/lib64/libdl-2.12.so
fmem
n/lib64/libc-2.12.so
fmem
n/lib64/libtinfo.so.5.7
fmem
n/usr/lib/locale/locale-archive
fmem
n/usr/lib64/gconv/gconv-modules.cache
f0
n/dev/pts/1
f1
n/dev/pts/1
f2
n/dev/pts/1
f255
n/home/joe/test/t.sh

我们必须转换这些混乱的内容,以便最高的文件描述符(我假设你不能依赖它是 255)是脚本名称。 (这似乎dash也适用。)

this_script_path=`lsof -p $$ -F fn | 
  perl -lane '
        $fd=$1,next if /^f(\d+)/; 
        $p{$fd}=$1 if $fd and /^n(.*)/;
        $fd="";
  }END { 
        @x=sort {$a<=>$b} keys %p;
        print $p{$x[-1]}; 
  }{'`

Perl 脚本很难看,我同意。这是一句单行话,为了清楚起见,我把它拆开了。如果该行以 开头,我们将捕获文件描述符的编号f;如果我们有有效的文件描述符和有效的文件名,我们将文件名捕获到哈希中。以防万一,如果这些条件都不满足,我们就会清除$fd。处理完所有行后,我们对哈希的键(文件描述符)进行数字排序,将结果存储到数组中x,并输出文件名哈希的内容p,由数组中的最后一个元素(最大值)索引x

唯一的问题是:lsof 是否会安装在所有系统上以及这种输出格式的稳定性如何。

答案2

有一些启发式方法可以帮助您,但没有完全可靠的方法。

Otheus 展示了如何使用文件描述符。这是一个很好的启发式方法,在大多数情况下都有效。然而,也有一些边缘情况会失败,并且无法检测到失败。

示例:采用以下脚本。

#!/bin/sh
set
lsof -p$$ | sed 's/[0-9][0-9]*//'

将该脚本复制两份,一份名为foo,一份名为bar。现在让我们稍微强调一下:

$ env -i PATH=/bin:/usr/bin perl -MPOSIX -e 'dup2(4, 11) or die $!; exec "dash", "foo", "bar"' 3<foo 4<bar </dev/null >foo.out
$ env -i PATH=/bin:/usr/bin perl -MPOSIX -e 'dup2(3, 10) or die $!; exec "dash", "bar", "foo"' 3<foo 4<bar </dev/null >bar.out
$ diff foo.out bar.out

17c17 <破折号吉尔斯1w REG 0,24 99 10130024 /tmp/202954/foo.out ---

短划吉尔斯 1w REG 0,24 99 10130022 /tmp/202954/bar.out

这里唯一的区别是我记录输出的文件。

这种启发式方法失败的另一种情况是,如果 shell 是在标准输入上调用的,或者使用-c.

另一种方法是解析 shell 的命令行。在 Linux 上,您可以通过/proc.参数是空分隔的,这很难用可移植的 shell 工具解析(最近的 GNU 工具使它更容易),但这是可以做到的。可移植的是,您需要调用ps来访问参数,并且没有明确的输出格式:ps -o args用空格连接参数,并且可能会被截断。

即使在 Linux 上,您在这里也会遇到的问题是,可能使用脚本不知道的选项来调用 shell,并且这些选项之一可能带有参数。例如,假设您有一个名为 的脚本1,另一个名为 的脚本2

mksh -T 1 2

这将调用 mksh on/dev/tty1并运行脚本2

zsh -T 1 2

这将使用选项调用 zsh并使用参数cprecedences运行脚本。12

您需要了解各个 shell 才能区分它们。使用此方法,您可以检测边缘情况:如果您看到非标准选项,请退出。

相关内容