2016-04-07 3 views
1

Ich benutze Perl, um ein externes Programm auszuführen, und möchte die Ausführung beenden, wenn es während der Ausführung eine bestimmte Zeichenfolge zurückgibt. Der folgende Code unterbricht die Ausführung wie gewünscht, jedoch wird eine Fehlermeldung zurückgegeben, wenn die letzte Zeile (close) ausgeführt wird.Perl schließt Pipe ohne Fehler

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

Dies ist der Teil des Fehlers, der gedruckt wird (das externe Programm liefert auch Fehlermeldungen):

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

Also ich denke, es ist, weil Perl zu einem geschlossenen Griff zu schreiben versucht. Wie kann ich vermeiden, dass etwas gedruckt wird?

+0

Perl versucht nicht zu schreiben, das ist ein Rohr zu lesen, nicht zu schreiben. Es scheint, als wäre dein externes Programm derjenige, der den Fehler gibt. –

Antwort

4

Sie schließen das externe Programm nicht, wenn Sie die STDOUTclose $out schließen.

Um das Programm zu schließen, müssen Sie seine Prozess-ID erhalten und dann ein geeignetes Signal senden. Der open Aufruf gibt die PID zurück, speichern Sie sie einfach. Dann sende ein Signal, wenn die Bedingung erfüllt ist.

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: $!"; 
} 

Siehe open und opentut und close.

Fortran des Fehler ist in der Tat über (STDOUT) zu einer nicht vorhandenen Konsole zu schreiben, so scheint es mir, dass Sie das Problem richtig erkannt: als die Bedingung erfüllt ist, können Sie aufhören zu lesen (last) und dann das Programm der schließen STDOUT , die den gemeldeten Fehler beim nächsten Schreibversuch verursacht.

Ein paar Anmerkungen. Das Schließen des Rohrs wartet darauf, dass der andere Prozess beendet wird. Wenn das Programm am anderen Ende ein Problem hat, das unter close bekannt ist, muss möglicherweise $? abgefragt werden. Wenn die Pipe geschlossen wird, bevor das andere Programm fertig ist, wird es gesendet SIGPIPE. Von close Dokumentation

Wenn die Dateikennung von einem verrohrt öffnen, schließen kehrt kam falsch, wenn einer des anderen syscalls beteiligt ausfällt oder wenn sein Programm beendet mit nicht-Null-Status. Wenn das einzige Problem darin bestand, dass das Programm nicht von Null ging, $! wird auf 0 gesetzt. Das Schließen einer Pipe wartet auch darauf, dass der Prozess, der auf der Pipe ausgeführt wird, beendet wird - falls Sie später die Ausgabe der Pipe sehen wollen - und fügt implizit den Exit-Status-Wert dieses Befehls in $ ein. und $ {^ CHILD_ERROR_NATIVE}.
...
Schließen Sie das Leseende einer Pipe, bevor der Prozess am anderen Ende schreibt, um Ergebnisse in den Writer zu schreiben, der ein SIGPIPE empfängt. Wenn das andere Ende nicht damit umgehen kann, lesen Sie alle Daten, bevor Sie die Pipe schließen.


Anmerkung hinzugefügt   Die Aufgabe ist, ein Verfahren zu töten, während seine STDOUT mit einer Dateikennung verbunden ist und dass Dateihandle zu schließen (sobald eine Bedingung erfüllt ist). Wenn wir zuerst den Handle schließen, während der Prozess noch schreiben kann, senden wir einen SIGPIPE an einen Prozess, über den wir vielleicht nicht viel wissen. (Geht es mit dem Signal um?) Auf der anderen Seite, wenn wir es zuerst beenden, kann die close($out) nicht mit Erfolg abgeschlossen werden, da der Prozess, der $out verbunden war, weg ist.Diese

Deshalb wird der Code nicht die Rückkehr überprüfen aus dem Aufruf von close nachdem der Prozess abgebrochen wird, wie es ist falsch (wenn kill gelungen). Nach der Schleife prüft es, ob der Griff noch offen ist, bevor er geschlossen wird, da wir ihn möglicherweise bereits geschlossen haben. Das Paket Scalar::Util wird dafür verwendet, eine andere Option ist fileno. Beachten Sie, dass der Code nicht prüft, ob kill den Job ausgeführt hat, fügen Sie diesen nach Bedarf hinzu.


Auf einem Windows-System ist es ein wenig anders, da Sie die Prozess-ID des Kindes suchen müssen. Sie können dies unter Verwendung seines Namens tun und ihn dann entweder unter Verwendung von Win32::Process::Kill oder unter Verwendung von kill beenden. (Oder finden Sie einen Windows-Befehl, der all dies tun sollten, unten.) Um die ID-Prozess versuchen, entweder von

  • Mit 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; 
        } 
    } 
    
  • Mit Win32::Process::Info. Siehe 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; 
        } 
    } 
    

    Beachten Sie, dass dieses Modul stellt auch das Verfahren Subprocesses([$ppid,...]), die alle Kinder der eingereichten $ppid identifizieren (s). Es gibt einen Hash zurück, der von $ppid s indiziert wird und ein Array-Ref mit $pid s aller Unterprozesse enthält, für die jeweils $ppid eingereicht wurde.

    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 
    } 
    
  • Ich lief in einem Windows-Befehl TASKKILL /T, die einen Prozess und seine Kinder

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

ich jetzt jede dieses Rechts beenden sollte nicht testen können.

+0

Wenn ich 'kill' verwende, wird nur die ID des übergeordneten Prozesses (cmd.exe) geschlossen. Ich habe 'kill -9, $ pid;' versucht, aber das macht dasselbe. Ich muss den Kindprozess (usfos.exe) schließen, um den Fehler zu vermeiden, aber nicht sicher, wie man seine ID erhält. Alternativ könnte ich STRG + c ("\ x03") an cmd senden, was den Kindprozess abbricht, aber nicht sicher ist, wie das geht. –

+0

@charleshendry Ich habe meine Antwort mit Windows-Möglichkeiten aktualisiert. Ich kann jetzt nicht testen. – zdim