Perl关闭管道没有错误

时间:2016-04-07 07:57:07

标签: perl pipe

我正在使用Perl来执行外部程序,并且如果在运行时返回特定字符串,则希望结束执行。下面的代码根据需要中断执行,但是在执行最后一行(关闭)时会返回错误消息。

open (my $out, "-|", "usfos $memory<input-$cpu.scr");
while (<$out>) {
    if ($_ =~ /MIN   STEP  LENGTH/) {
        last;
    }
}
close $out;

这是打印错误的一部分(外部程序也会返回错误消息):

...forrtl: The pipe is being closed.
forrtl: severe (38): error during write, unit 6, file CONOUT$

所以我认为这是因为Perl试图写一个封闭的句柄。如何避免打印任何内容?

1 个答案:

答案 0 :(得分:4)

当您close $out关闭STDOUT时,您不会终止外部程序。

要关闭程序,您需要获取其进程ID,然后向其发送适当的信号。 open调用返回pid,只保存它。然后在满足条件时发送信号。

use Scalar::Util qw(openhandle); # to check whether the handle is open

my $pid = open (my $out, "-|", "usfos $memory<input-$cpu.scr") 
                // die "Can't fork: $!";   # / stop editor red coloring
while (<$out>) {
    if (/MIN   STEP  LENGTH/) {
        kill "TERM", $pid;   # or whatever is appropriate; add checks
        close $out;          # no check: no process any more so returns false
        last;
    }
}
# Close it -- if it is still open. 
if (openhandle($out)) {          
    close $out or warn "Error closing pipe: $!";
}

请参阅openopentut以及close

Fortran的错误确实是关于写入不存在的控制台(STDOUT),所以在我看来你是否正确诊断了问题:当条件匹配时,你停止阅读(last )然后关闭程序的STDOUT,这会导致在下次尝试写入时报告错误。

一些笔记。关闭管道等待其他过程完成。如果另一端的程序存在将在close知道的问题,则可能需要询问$?。在另一个程序完成写入之前关闭管道将在下次写入该管道时发送它SIGPIPE。来自close 文档

  

如果文件句柄来自管道打开,则如果其中一个其他系统调用失败或其程序以非零状态退出,则close返回false。如果唯一的问题是程序退出非零,$!将被设置为0。关闭管道还会等待管道上执行的进程退出 - 如果您希望之后查看管道的输出 - 并隐式将该命令的退出状态值放入$?和$ {^ CHILD_ERROR_NATIVE}   ...
  在将进程写入另一端的进程之前关闭管道的读取结束,写入结果在写入器接收SIGPIPE。如果另一端无法处理,请务必在关闭管道之前读取所有数据。

添加注释对象是在STDOUT连接到文件句柄以关闭该文件句柄(一旦满足条件)时终止进程。如果我们在进程可能仍在写入时首先关闭句柄,我们会向SIGPIPE发送一个我们可能不太了解的进程。 (它是否处理信号?)另一方面,如果我们首先终止它,close($out)无法成功完成,因为$out连接的进程已经消失。

这就是为什么代码在进程被终止后不会检查从close调用返回的原因,因为它 为false(如果kill成功)。在循环之后,它会在关闭之前检查手柄是否仍然打开,因为我们可能已经或可能尚未关闭它。包Scalar::Util用于此,另一个选项是fileno。请注意,代码不会检查kill是否完成了该作业,并根据需要添加该作业。

在Windows系统上,它有点不同,因为您需要查找子进程ID。您可以使用其名称执行此操作,然后使用Win32::Process::Kill或使用kill将其终止。 (或者,请参阅下面应该执行所有操作的Windows命令。)要查找进程ID,请尝试使用

  • 使用Win32::Process::List

    use Win32::Process::Kill;
    use Win32::Process::List;
    my $pobj = Win32::Process::List->new(); 
    my %proc = $pobj->GetProcesses(); 
    my $exitcode;
    foreach my $pid (sort { $a <=> $b } keys %proc) {
        my $name = $proc{$pid};
        if ($name =~ /usfos\.exe/) {
            Win32::Process::KillProcess($pid, \$exitcode);
            # kill 21, $pid;
            last;
        }
     }
    
  • 使用Win32::Process::Info。请参阅this post

    use Win32::Process::Info;
    Win32::Process::Info->Set(variant=>'WMI');   # SEE DOCS about WMI
    my $pobj = Win32::Process::Info->new();
    foreach my $pid ($pobj->ListPids) {
        my ($info) = $pobj->GetProcInfo($pid);
        if ($info->{CommandLine} =~ /^usfso/) {  # command-line, not name
            my $proc = $info->{ProcessId};
            kill 2, $proc; 
            last;
        }
    }
    

    请注意,此模块还提供方法Subprocesses([$ppid,...]),该方法将标识提交的$ppid的所有子项。它返回一个哈希值,该哈希值由$ppid索引,并包含一个包含所有子进程$pid的数组-ref,用于提交的每个$ppid

    use Win32::Process::Info;
    Win32::Process::Info->Set(variant=>'WMI');
    my $pobj = Win32::Process::Info->new();
    my %subproc = $pobj->Subprocesses([$pid]);   # pid returned by open() call
    my $rkids = $subproc{$pid};
    foreach my $kid (@$rkids) {
        print "pid: $kid\n";                     # I'd first check what is there
    }
    
  • 我遇到了一个Windows命令TASKKILL /T,它应该终止进程及其子进程

    system("TASKKILL /F /T /PID $pid");
    

我现在无法测试任何此类内容。