如何在Perl中确定Java进程是否正在运行

3

我有一套小型Java应用程序,所有应用都编译/打包成<name-of-the-app>.jar并运行在我的服务器上。偶尔其中一个会抛出异常,卡住并停止运行。我正在尝试编写一个快速而简单的Perl脚本,定期轮询以查看这些可执行JAR文件是否仍在运行,如果有任何一个没有运行,则会发送电子邮件通知我哪个已停止。

要手动确定这一点,我必须为每个要检查的应用运行ps -aef | grep <name-of-app>命令。例如,要查看myapp.jar是否作为进程运行,我运行ps -aef | grep myapp,然后查找描述表示myapp.jar的JVM进程的grep结果。这种手动检查现在变得乏味,是自动化的主要候选项!

我正试图实现检查进程是否正在运行的代码。我想将其作为一个sub来实现,该sub接受可执行JAR的名称并返回truefalse

sub isAppStillRunning($appName) {
    # Somehow run "ps -aef | grep $appName"

    # Somehow count the number of processes returned by the grep

    # Since grep always returns itself, determine if (count - 1) == 1.
    # If so, return true, otherwise, false.
}

我需要能够传递应用程序的名称给sub,运行我的正常命令,并计算grep返回的结果数。由于运行grep总是至少有一个结果(grep命令本身),我需要一种逻辑来判断如果(结果数-1)等于1,则我们知道该应用正在运行。
我刚开始学习Perl,很难弄清楚如何实现这个逻辑。以下是到目前为止我最好的尝试:
sub isAppStillRunning($appName) {
    # Somehow run "ps -aef | grep $appName"
    @grepResults = `ps -aef | grep $appName`;

    # Somehow count the number of processes returned by the grep
    $grepResultCount = length(@grepResults);

    # Since grep always returns itself, determine if (count - 1) == 1.
    # If so, return true, otherwise, false.
    if(($grepResultCount - 1) == 1)
        true
    else
        false
}

然后要调用该方法,从同一个Perl脚本内部,我认为只需运行:

&isAppStillRunning("myapp");

非常感谢您对定义子应用程序并使用正确的应用程序名称进行调用的任何帮助。 提前致谢!


Perl可以做到这一点,但这是重新发明轮子。有许多系统管理员解决方案可用于重新启动进程。 - pilcrow
关于在进程表中匹配其参数的grep,通常避免这种情况的方法是使用grep some[a]pp而不是grep someapp。此外,grep -c some[a]pp将可移植地给出匹配的计数。 - pilcrow
除了@pilcrow所说的,我过去还做过grep process_name | grep -v grep的操作,以避免在输出中获取到grep进程。 - laaph
3个回答

4

如果使用CPAN上的Proc::ProcessTable模块,将会轻松许多(约10亿倍)。以下是一个示例:

use strict;
use warnings;
use Proc::ProcessTable;

...
sub isAppStillRunning { 
    my $appname = shift;
    my $pt = Proc::ProcessTable->new;
    my @procs = grep { $_->fname =~ /$appname/ } @{ $pt->table };

    if ( @procs ) { 
        # we've got at least one proc matching $appname. Hooray!
    } else { 
        # everybody panic!
    }
}

isAppStillRUnning( "myapp" );

需要注意的几点:

  • 打开strictwarnings,它们是你的朋友。
  • 不使用原型指定子例程参数。(在Perl中,原型会执行完全不同的操作,你不需要这个。)使用shift@_数组中获取参数。
  • 不要使用&调用子程序;直接使用其名称即可。
  • 标量上下文中计算的数组(包括它是否在if语句中)给出其大小。在数组上不适用length方法。

3

您的代码基本正确,但最后的if-else结构需要修正,并且在某些情况下,Perl习语可以让您的生活更轻松。

Perl有原型,但它们很糟糕

sub isAppStillRunning($appName) {

不会起作用。请改用

sub isAppStillRunning {
  my ($appName) = @_;

@_ 数组保存了函数的参数。Perl有一些简单的原型(如 sub name(&$@) {...} 语法),但是它们已经过时,而且属于高级主题,所以不要使用它们。

Perl内置了Grep函数

`ps -aef | grep $appName`;

这将返回一个字符串,可能包含多行。您可以在换行处拆分输出,并手动使用grep,这比插值变量更安全:

my @lines   = split /\n/ `ps -aef`;
my @grepped = grep /$appName/, @lines;

您也可以使用 open 函数来显式打开到 ps 的管道:
my @grepped = ();
open my $ps, '-|', 'ps -aef' or die "can't invocate ps";
while (<$ps>) {
  push @grepped if /$appName/;
}

这样写法完全相等,但更好的风格。它从ps输出中读取所有行,然后将所有包含$appName的行推入@grepped数组。

标量上下文与列表上下文

Perl语言有一个不同寻常的概念叫做“上下文”(context)。有列表上下文标量上下文之分。例如,子例程调用需要参数列表 - 因此这些列表通常具有列表上下文。而连接两个字符串则是标量上下文。

数组在其上下文中表现不同。在列表上下文中,它们被解析为它们的元素,但在标量上下文中,它们被解析为它们的元素数量。因此,没有必要手动计算元素的数量(或使用适用于字符串的length函数)。

所以我们有:

 my @array = (1, 2, 3);
 print "Length: ", scalar(@array), "\n"; # prints "Length: 3\n"
 print "Elems: ", @array, "\n";          # prints "Elems: 123\n";
 print "LastIdx: ", $#array, "\n";       # prints "LastIdx: 2\n";

最后一个表单$#array是数组中的最后一个索引。除非你干涉特殊变量,否则它与@array - 1相同。
函数scalar强制标量上下文。
Perl没有布尔类型,因此没有truefalse关键词。相反,所有值都为真,除非另有说明。假值包括:空字符串""、数字零0、字符串零"0"、特殊值undef和其他一些你不会遇到的怪异值。
因此,通常使用1表示真,使用0表示假。
if/else语句需要花括号,所以你可能想要这样写:
if (TEST) {
  return 1;
} else {
  return 0;
}

这句话的意思是,它与return TEST相同,其中TEST是一个条件。

最终简化

使用这些技巧,你的子程序可以写得非常简短,例如:

sub isAppStillRunning {
   return scalar grep /$_[0]/, (split /\n/, `ps -aef`);
}

这将返回包含您的应用程序名称的行数。

哇!感谢 @amon (+1) - 绝佳的指引!那么,我假设我会调用子程序并通过调用 if(isAppStillRunning('myapp') == 1) 来检查应用程序是否正在运行?如果返回值为1,则我们知道有匹配,对吗?再次感谢! - IAmYourFaja
1
@4herpsand7derpsago 不,返回值不总是1。将返回值视为真或假值,即if (isAppStillRunning("yourapp"))而无需进一步条件。 - amon
啊,我明白了。但是如果“myapp”没有运行,我总是可以指望它为0,对吧? - IAmYourFaja
抱歉 @amon,最后一个问题!如果我想要检查"myapp"是否没有在运行,当Perl不支持布尔值时,我应该如何调用isAppStillRunning函数?我可以使用if(!isAppStillRunning("myapp"))吗?还是说我需要改变子程序内部的逻辑? - IAmYourFaja
只需否定返回值(就像你所做的那样)。可以使用!运算符;如果您喜欢冗长,也可以使用具有相同效果的not函数。 - amon
1
关于检查负条件,在Perl中一种优雅的选项是 unless (isAppStillRunning("myapp")) { ... }。这与 if (!...) 完全相同。 - dan1111

1
您可以像这样修改您的例程:
sub isAppRunning {
    my $appName = shift;
    @grepResults = `ps -aef | grep $appName`;
    my $items = 0;
    for $item(@grepResults){
        $items++;
    }
    return $items;
}

这将遍历 @grepResults 并允许您在必要时检查 $item。

像这样调用应该返回进程的数量:

print(isAppRunning('myapp') . "\n"); 

感谢 @jcern (+1) - 3个快速跟进:(1)您能解释一下第一行my @appName = shift的意思吗? shift让我有些困惑! (2)for 循环体周围的花括号是否强制性要求,或只是最佳实践?不确定Perl是否会强制执行大括号,即使只有一个语句。并且(3)Perl 是否不支持布尔真/假值?我只是想知道为什么您的子程序返回项计数而不是true/false。再次感谢! - IAmYourFaja
1
作为子进程运行 grep $appName 将包括 grep 进程在内进行计数。更准确的方法是使用 perl 捕获 ps -aef 的结果并进行 grep。 - Phssthpok
@4herpsand7derpsago(2)花括号几乎总是必需的,因为Perl是基于块而不是语句定义的,而块是由花括号界定的。(3)请参阅我对您问题的回答。 - amon
对于第一点,您可以这样考虑,函数的参数以数组形式存在。您可以从该数组中弹出(或移动)项目并将其分配给变量。对于(2),大括号通常是必需的。而(3)似乎已经在amon的回答中得到了很好的解答。 - jcern

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