2017-07-27 2 views
1

Ich versuche, Perl5 zu fork() einen untergeordneten Prozess zu verwenden. Der untergeordnete Prozess sollte exec() ein anderes Programm sein, das seine STDIN an eine Named Pipe und STDOUT und STDERR an Protokolldateien umleitet. Der übergeordnete Prozess wird weiterhin in einer Schleife ausgeführt, wobei waitpid verwendet wird und $? überprüft wird, um das untergeordnete Element neu zu starten, falls es mit einem Nicht-Null-Exit-Status endet.Redirect STDOUT und STDERR in exec() ... ohne Shell

Perl-Dokumentation für die exec() Funktion sagt:

Wenn es mehr als ein Argument in LIST ist, dies nennt execvp (3) mit den Argumenten in LIST. Wenn nur ein Element in LIST vorhanden ist, wird das Argument auf Shell-Metazeichen überprüft, und falls vorhanden, wird das gesamte Argument an die Befehlshell des Systems zum Parsen übergeben (dies ist /bin/sh -c auf Unix-Plattformen, variiert jedoch auf anderen Plattformen). Wenn das Argument keine Shell-Metazeichen enthält, wird es in Wörter aufgeteilt und direkt an execvp übergeben, was effizienter ist. Beispiele:

exec '/bin/echo', 'Your arguments are: ', @ARGV; 
exec "sort $outfile | uniq"; 

Das klingt ziemlich cool, und ich möchte mein externes Programm ohne Vermittler Shell auszuführen, wie in diesen Beispielen gezeigt. Leider kann ich dies nicht mit der Ausgabeumleitung kombinieren (wie in /bin/foo > /tmp/stdout).

Mit anderen Worten, das funktioniert nicht:

exec ('/bin/ls', '/etc', '>/tmp/stdout'); 

Also, meine Frage ist: Wie leite ich die STD* Dateien für meinen Unter Befehl, ohne die Schale mit?

Antwort

5

Umleitung über < und > ist eine Shell-Funktion, weshalb es in dieser Verwendung nicht funktioniert. Sie rufen im Wesentlichen /bin/ls und >/tmp/stdout als nur ein weiteres Argument übergeben, die gut sichtbar ist, wenn der Befehl von echo ersetzt:

exec ('/bin/echo', '/etc', '>/tmp/stdout'); 

druckt:

/etc >/tmp/stdout 

Normalerweise Shell (/bin/sh) würde analysiert haben der Befehl, entdeckte die Umleitungsversuche, öffnete die richtigen Dateien und beschnitt auch die Argumentliste, die in /bin/echo ging.

jedoch - Ein Programm begann mit exec() (oder system()) die STDIN, STDOUT und STDERR Dateien seiner Berufung Prozess erben. Also, die richtige Art und Weise zu handhaben ist zu

  • schließt jede spezielle Dateihandle,
  • wieder zu öffnen, sie in der gewünschten Logdatei zeigen und
  • schließlich exec() rufen Sie das Programm zu starten.

Umschreiben Ihres Beispielcode oben, das funktioniert gut:

close STDOUT; 
open (STDOUT, '>', '/tmp/stdout'); 
exec ('/bin/ls', '/etc'); 

... oder die indirekte Objektsyntax von perldoc empfohlen mit:

close STDOUT; 
open (STDOUT, '>', '/tmp/stdout'); 
exec { '/bin/ls' } ('ls', '/etc'); 

(in der Tat, nach In der Dokumentation ist diese letzte Syntax die einzige zuverlässige Möglichkeit, die Shell in Windows nicht zu instanziieren.

4

Die folgenden Der Befehl wing shell gibt der Shell den Befehl /bin/ls mit /etc für das Argument STDOUT umgeleitet zu starten.

/bin/ls /etc >/tmp/stdout 

Auf der anderen Seite, wird die folgende Perl-Anweisung teilt das Perl das aktuelle Programm mit /bin/ls, mit /etc und >/tmp/stdout für Argumente zu ersetzen.

exec('/bin/ls', '/etc', '>/tmp/stdout'); 

Sie haben Perl nicht gesagt, STDOUT überhaupt umzuleiten! Denken Sie daran, dass exec keinen neuen Prozess startet. Wenn Sie also die fd 1 des untergeordneten Prozesses ändern, wirkt sich dies auf ls im selben Prozess aus.

Aber nicht nur dieses ein Problem zu beheben (als Greg Kennedy tat), aber lassen Sie andere Probleme intakt (zB Fehlmeldungen, die Unfähigkeit ls als Fehler von ls der Einleitung), werde ich Ihnen zeigen, um sie zu beheben alles:

use IPC::Open3 qw(open3); 

my $stdout = ''; 
{ 
    # open3 will close the handle used as the child's STDIN. 
    # open3 has issues with lexical file handles. 
    open(local *CHILD_STDIN, '<', '/dev/null') or die $!; 

    my $pid = open3('<&CHILD_STDIN', local *CHILD_STDOUT, '>&STDERR', 
     '/bin/ls', '/etc'); 

    while (my $line = <CHILD_STDOUT>) { 
     $stdout .= $line; 
    } 

    waitpid($pid, 0); 
} 

Während das Sie hundert Zeilen Code gespeichert, ist open3 ein immer noch ziemlich Low-Level. (Sie werden Probleme bekommen, wenn Sie mit zwei Rohren umgehen müssen.) Ich empfehle stattdessen IPC::Run3 (einfacher) oder IPC::Run (flexibler).

use IPC::Run3 qw(run3); 
run3([ '/bin/ls', '/etc' ], \undef, \my $stdout); 

oder

use IPC::Run qw(run); 
run([ '/bin/ls', '/etc' ], \undef, \my $stdout);