答案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
运行脚本。1
2
您需要了解各个 shell 才能区分它们。使用此方法,您可以检测边缘情况:如果您看到非标准选项,请退出。