Perl关闭管道没有错误

cha*_*dry 2 perl pipe

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

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

这是所显示的错误的一部分(外部程序还会返回错误消息):

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

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

zdi*_*dim 5

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: $!";
}
Run Code Online (Sandbox Code Playgroud)

参见 openopentut,然后 关闭

Fortran的错误确实与写入不存在的控制台(STDOUT)有关,因此在我看来,您已正确诊断了该问题:当条件匹配时,您停止读取(last),然后关闭程序的STDOUT,这将导致报告错误。下一次尝试写入。

一些注意事项。close管道上的A 等待其他过程完成。如果另一端的程序有打算在被称为一个问题close,所以$?可能需要询问。如果在另一个程序完成写入之前关闭了管道,则在下一次写入该管道时,该程序将得到一个SIGPIPE。从 文档

如果文件句柄来自管道打开,则如果所涉及的其他系统调用之一失败或它的程序以非零状态退出,则close返回false。如果唯一的问题是程序退出了非零值,则$!将设置为0。关闭管道还等待管道上执行的进程退出(以防您以后要查看管道的输出),并将该命令的退出状态值隐式地放入$?。和$ {^ CHILD_ERROR_NATIVE}。
...
在完成另一端的写入过程之前,先关闭管道的读取端,然后在写入器中接收到SIGPIPE的结果。如果另一端无法处理,请确保在关闭管道之前先读取所有数据。


注意   目标是在将进程STDOUT连接到文件句柄时将其杀死,关闭该文件句柄(一旦满足条件)。如果我们在进程可能仍在写时先关闭句柄,那么我们正在向SIGPIPE可能不太了解的进程发送a 。(它处理信号吗?)另一方面,如果我们首先终止它close($out),则由于$out连接的过程已消失,因此无法成功完成。

这就是为什么代码close在进程终止后不检查从调用返回的原因,因为它假的(如果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;
        }
     }
    
    Run Code Online (Sandbox Code Playgroud)
  • 使用Win32 :: Process :: Info。看到这篇文章

    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;
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    请注意,此模块还提供了方法Subprocesses([$ppid,...]),该方法将标识提交的所有子级$ppid。对于每个提交的对象,它返回一个哈希值,该哈希值由$ppids 索引,并包含$pid所有子进程的s 数组引用$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
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • 我遇到了Windows命令TASKKILL /T,该命令应终止进程及其子进程

    system("TASKKILL /F /T /PID $pid");
    
    Run Code Online (Sandbox Code Playgroud)

我现在无法测试任何Windows代码。