【问题标题】:Perl: Monitor a background process's output without waiting for it to completePerl:监视后台进程的输出而不等待它完成
【发布时间】:2012-08-23 18:57:54
【问题描述】:

我正在尝试用 Perl 编写一个管理器来自动化我实验室一直在使用的生物信息学管道。 (REPET 管道,任何感兴趣的人都可以使用。)管道有八个步骤,其中几个被分解为可以并行运行的子步骤。最值得注意的是,步骤 3 分为三个部分,步骤 4 分为三个相应的部分。 step 3 的每个部分都可以独立运行,其对应的 step 4 部分可以在其 step 3 同伴完成后立即启动。我希望我的经理能够在三个并行线程中启动第 3 步,并且对于每个线程,在第 3 步完成后立即转到第 4 步。我能想到的最好方法是监控每个进程的输出。每个步骤的输出如下所示:

START TEdenovo.py (2012-08-23 11:20:10)
version 2.0
project name = dm3_chr2L
project directory = /home/<etc>
beginning of step 1
submitting job(s) with groupid 'dm3_chr2L_TEdenovo_prepareBatches' (2012-08-23 11:20:10)
waiting for 1 job(s) with groupid 'dm3_chr2L_TEdenovo_prepareBatches' (2012-08-23 11:20:10)
execution time per job: n=1 mean=2.995 var=0.000 sd=0.000 min=2.995 med=2.995 max=2.995
step 1 finished successfully
version 2.0
END TEdenovo.py (2012-08-23 11:20:25)

这是第 1 步的输出,但是在第 3 步中,当“第 3 步成功完成”出现在输出中时,可以安全地继续进行第 4 步。问题已成功将其中三个进程的输出制表为他们立刻跑了。本质上,这是我想要的行为(伪代码):

my $log31 = `TEdenovo.py [options] &`;
my $log32 = `TEdenovo.py [options] &`;
my $log33 = `TEdenovo.py [options] &`;

while(1) {
    #start step 41 if $log31 =~ /step 3 finished successfully/;
    #start step 42 if $log32 =~ /step 3 finished successfully/;
    #start step 43 if $log33 =~ /step 3 finished successfully/;
    #monitor logs 41, 42, 43 similarly
    last if #all logs read "finished successfully"
    sleep(5);
}

#move on to step 5

问题是用反引号调用一个进程会导致 perl 等到该进程完成才能继续前进;正如我发现的那样,它与 system() 不同,您可以在其中使用 & 将某些内容旋转到后台进程中,然后立即继续。据我所知,没有一个好的方法可以使用 system() 来获得我正在寻找的效果。我想我可以这样做:

system("TEdenovo.py [options] & > log31.txt");

然后定期轮询 log31.txt 看看是否出现了“已成功完成”,但这似乎不必要的混乱。

我也尝试在文件句柄中打开进程:

open(my $step3, "TEdenovo.py [options] |");
my @log3;

while(1)
{
    push(@log3, <$step3>);
    last if grep("step 3 finished successfully", @log3);
    sleep(5);
}

...但是,再一次,Perl 一直等到进程完成才能继续前进(在这种情况下,在 push() 处)。我用 $| 尝试了上述方法设置和取消设置。

所以,我的问题的本质是:有没有办法在 perl 中捕获正在运行的后台进程的标准输出?

【问题讨论】:

    标签: perl process background monitor


    【解决方案1】:

    也许你可以试试

    open(my $step3, "TEdenovo.py [options] |");
    
    while(<$step3>)
    {
        last if /step 3 finished successfully/;
    }
    

    而不是 while(1) ?

    【讨论】:

    • push @log3,&lt;$step3&gt; 在列表上下文中评估 &lt;$step3&gt;,它等待 eof(即,等待进程完成)。
    • 啊哈,伙计们,你的建议似乎已经做到了。我没有读入一个数组,而是尝试一次连接一行,如$log3 .= &lt;$step3&gt;,它正像我希望的那样进入等待循环。现在只剩下一个小问题:以这种方式一次只能获取一行输出,而我想每次都将其全部吞下。使用文件 slurp 模式 $/ = undef 也会搜索 EOF,不是吗?那么我如何才能在每一步获得全部输出呢?如果没有办法,我不会感到惊讶;毕竟,这就是 EOF 的用途,不是吗?
    • 我仍然使用while(1) 而不是 Nahuel 的建议,因为我不会将所有可用的循环条件信息都放入循环中。我需要监视八个进程的 EOF,当我进入循环时,其中只有两个会启动。即,我还没有打开这些文件句柄,并且 eof() 为未打开的文件句柄返回 1,这将错误地评估为循环的退出条件。因此,我还必须将每个日志的内容作为循环条件进行监控……它只会变得混乱;在这种情况下,更容易从循环引脚中监控循环条件。
    【解决方案2】:

    使用open 并从管道句柄读取的方法是 正确的方法。如果 Nahuel 建议在标量上下文中从句柄读取没有帮助,您可能仍然会受到缓冲的影响。

    $| 改变 Perl 输出的缓冲行为,但不会改变从 Perl 调用的任何外部程序的行为。您必须使用不缓冲其输出的外部程序。在这种情况下,我相信通过将 -u 选项传递给 python 是可能的:

    open(my $step3, "|-", "python -u TEdenovo.py [more options]");
    

    【讨论】:

      猜你喜欢
      • 2011-03-25
      • 2021-07-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-08
      • 2012-03-04
      相关资源
      最近更新 更多