我正在使用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试图写一个封闭的句柄。如何避免打印任何内容?
答案 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: $!";
}
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,请尝试使用
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");
我现在无法测试任何此类内容。