我该如何找出是哪个脚本、程序或Shell执行了我的Perl脚本?

7
如何确定是哪个脚本、程序或者shell执行了我的Perl脚本?
例如:如果从Shell中运行,我可能需要人类可读的输出(每种类型的Shell进行自定义),如果从另一个Perl脚本调用,则需要不同类型的输出,并且如果从连续集成服务器等程序执行,则需要机器可读的格式。
动机:我有一个工具,根据执行它的Shell改变其输出。通常我会实现此行为作为脚本的选项,但是该工具的设计不允许使用选项。其他Shell拥有指示正在运行的Shell的环境变量。我正在编写一个补丁以支持没有这样特殊变量的PowerShell。
编辑:这些答案中许多都是Linux特定的。不幸的是,PowerShell适用于Windows。getppid、$ENV{SHELL}变量和使用ps均无法帮助解决此问题。该脚本需要跨平台运行。

我不确定我理解了 - 在那种情况下,你不应该看父进程吗? - x0n
你可以测试一下STDOUT是否是终端(-t),如果是,就可以假设你是从交互式shell中调用的,而不是从持续集成守护程序中调用的。然而,这种常见的技术并不能针对每个可能的父进程进行自定义输出(但我认为这是一个有些错误的愿望)。 - pilcrow
忘了提到,通常它会查找一个环境变量,该环境变量由shell或用户设置以指示运行的shell是什么;但是在PowerShell中,没有这样的环境变量。 - Robert P
@RobertP 我找到了一个检测PowerShell 2或更高版本的技巧。请看下面的答案。更新 - 哦,有人比我先发现了它。删除我的答案。 - x0n
顺便提一下,当您检测到 PowerShell 时,可以发出 clixml 序列化文本,PowerShell 将自动将文本反序列化为对象。很酷,对吧? - x0n
显示剩余2条评论
5个回答

5

你使用getppid()。在child.pl中使用以下代码片段:

my $ppid = getppid();
system("ps --no-headers $ppid");

如果您从命令行运行它,system会显示bash或类似的内容(以及其他内容)。在另一个脚本中使用system("perl child.pl");执行它,例如parent.pl,您将看到perl parent.pl执行它。
为了仅捕获带参数的进程名称(感谢ikegami提供正确的ps语法):
my $ppid = getppid();
my $ps = `ps --no-headers -o cmd $ppid`;
chomp $ps;

编辑: 这种方法的替代方案可能是创建软链接到您的脚本,使不同的上下文使用不同的链接来访问您的脚本,并检查$0以围绕其构建逻辑。


2
更加可靠的写法:chomp( my $parent_name = \ps --no-headers -o cmd $ppid` );` - ikegami
@ikegami:谢谢。:) 我不知道如何指定格式。 - flesk
Robert P,你所说的并不完全正确,这取决于情况。使用Cygwin,大部分都可以正常工作(至少是我已经在一个项目中使用的外壳部分)。 - Marius Kjeldahl
@MariusKjeldahl - 你在说什么?请读一下问题。OP说他们正在为Powershell工作补丁。 - manojlds
@Marius:Powershell是一个shell。它恰好是Windows的一个shell。 - Robert P
显示剩余4条评论

3
我建议采用不同的方法来实现你的目标。不要猜测上下文,而是让它更加明确。每个用例都是完全独立的,因此需要三个不同的接口。
1. 一个可以在Perl程序中调用的函数。这可能会返回一个Perl数据结构。这比解析脚本输出更容易、更快捷、更可靠。它也可以作为脚本的基础。
2. 一个为当前shell输出的脚本。它可以查看$ENV{SHELL}以发现正在运行的shell。如果提供一个开关来显式覆盖,那就更好了。
3. 一个可以在非Perl程序(例如您的持续集成服务器)中调用并发出机器可读输出的脚本。XML和/或JSON等。
2和3只是对从1中输出的数据进行格式化的薄包装器。
每个接口都根据其特定需求量身定制。每个接口都可以在不使用启发式技术的情况下正常工作。每个接口都比试图猜测上下文和用户想要什么简单得多。
如果无法分离2和3,请让持续集成服务器设置环境变量并查找它。

同意,在一般情况下是这样的。就像我所提到的,以上只是这种技术可能应用的例子。在这种情况下,脚本会发出一个shell脚本,并且其他工具希望它能够为调用脚本的shell发出正确类型的shell脚本。对于几乎所有其他shell,都有环境变量指示要使用哪个shell。Powershell没有提供这样的变量。 - Robert P

1

这是在Windows XP上使用PowerShell v2.0,所以请谨慎对待。

cmd.exe shell中,我得到:

PSModulePath=C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\

然而在 PowerShell 控制台窗口中,我得到:

PSModulePath=E:\Home\user\WindowsPowerShell\Modules;C:\WINDOWS\system32\WindowsP owerShell\v1.0\Modules\

E:\Home\user 是我的“我的文档”文件夹所在的位置。因此,一个启发式方法是检查 PSModulePath 是否包含用户相关路径。

此外,在控制台窗口中,我得到:

!::=::\

在 PowerShell ISE 中,我得到:

!::=::\
!C:=C:\Documents and Settings\user

+1 - 啊,糟糕。我甚至没有注意到你的答案。我刚刚也发现了同样的事情。顺便说一下,那不是PowerShell 1.0 - v1不支持模块。在%windir%下的路径是v1.0,适用于PowerShell v1、v2和v3。 - x0n

1

如果您正在运行 PowerShell 2.0 或更高版本(最有可能的情况),则可以通过检查环境变量 %psmodulepath% 来推断 shell 作为父进程。默认情况下,它指向 %windir%\system32\windowspowershell\v1.0\modules 下的系统模块;如果您从 cmd.exe 检查该变量,则会看到这一点。

然而,当 PowerShell 启动时,它会将用户的默认模块搜索路径添加到此环境变量中,该路径看起来像是: %userprofile%\documents\windowspowershell\modules。这被子进程继承。因此,您的逻辑应该是测试 %psmodulepath% 是否以 %userprofile% 开头,以检测 PowerShell 2.0 或更高版本。在 PowerShell 1.0 中,这种方法不起作用,因为它不支持模块。


1
根据您的环境,您可以从环境变量中获取它。考虑以下代码:
/usr/bin/perl -MData::Dumper -e 'print Dumper(\%ENV);' | grep sh

在我的Ubuntu系统上,它会给我:

'SHELL' => '/bin/bash',

所以我猜这意味着我正在从bash shell运行perl。如果您使用其他东西,则SHELL变量可能会给您一些提示。

但是假设您知道自己在bash中,但perl是从子shell中运行的。然后尝试:

/bin/sh -c "/usr/bin/perl -MData::Dumper -e 'print Dumper(\%ENV);'" | grep sh

您将会找到:

      '_' => '/bin/sh',
      'SHELL' => '/bin/bash',

因此,shell 仍然是 bash,但是 bash 有一个变量 $_,它也显示正在执行的 shell 或脚本的绝对文件名,这可能会给出有价值的提示。同样地,在其他环境中,Perl 的 %ENV 哈希表中很可能留下了一些提示,这些提示应该会给你有价值的线索。


啊,终于明白为什么我被踩了。直到现在才看到 PowerShell 标签。我的错。 - Marius Kjeldahl
不是我给你点了踩,但我希望有一个环境变量告诉我PowerShell正在运行。不幸的是,PowerShell没有这样的变量。该脚本确实使用环境来确定为其他shell类型输出什么内容。 - Robert P
从这里http://vlaurie.com/computers2/Articles/environment-variables-windows-vista-7.htm看来,默认情况下COMSPEC指向cmd.exe;当在PowerShell下运行时,它可能指向PowerShell可执行文件?如果是这样,您应该能够从环境中找出运行时环境,包括Cygwin和类似的东西... - Marius Kjeldahl
我希望 :-) 不幸的是,Powershell不能更改那个变量。我在Windows中对比了Powershell和传统命令提示符之间的全面差异,唯一表明你不在该命令提示符中的指示是它缺少“PROMPT”变量!这不是最可靠的指标。 :) - Robert P

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