2017-07-27 1 views
1

Je tente d'utiliser Perl5 à fork() un processus enfant. Le processus enfant doit exec() un autre programme, en redirigeant son STDIN vers un canal nommé, et STDOUT et STDERR vers les fichiers journaux. Le processus parent continue à s'exécuter dans une boucle, en utilisant waitpid et en vérifiant $? pour redémarrer l'enfant au cas où il meurt avec un statut de sortie différent de zéro.Rediriger STDOUT et STDERR dans exec() ... sans shell

documentation Perl pour la fonction exec() dit:

S'il y a plus d'un argument dans LIST, cela exige execvp (3) avec les arguments en LIST. S'il n'y a qu'un seul élément dans LIST, l'argument est vérifié pour les métacaractères shell, et s'il y en a, l'argument entier est passé à l'interpréteur de commandes du système (/bin/sh -c sur les plateformes Unix, mais varie sur d'autres plateformes). S'il n'y a pas de métacaractères shell dans l'argument, il est divisé en mots et transmis directement à execvp, ce qui est plus efficace. Exemples:

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

Cela semble assez cool, et je voudrais lancer mon programme externe sans coquille intermédiaire, comme le montrent ces exemples. Malheureusement, je suis incapable de combiner cela avec la redirection de sortie (comme dans /bin/foo > /tmp/stdout).

En d'autres termes, cela ne fonctionne pas:

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

Alors, ma question est la suivante: comment puis-je rediriger les fichiers STD* pour mon sous-commande, sans utiliser le shell?

Répondre

5

Redirection via < et > est une fonction de coque , ce qui est pourquoi il ne fonctionne pas dans cet usage. Vous essentiellement appelez /bin/ls et passant >/tmp/stdout comme un autre argument, qui est facilement visible lors du remplacement de la commande par echo:

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

impressions:

/etc >/tmp/stdout 

Normalement, votre shell (/bin/sh) aurait analysables la commande, repéré les tentatives de redirection, ouvert les fichiers appropriés, et également élagué la liste des arguments allant à /bin/echo.

Cependant - Un programme a commencé avec exec() (ou system()) héritera des STDIN, STDOUT et STDERR fichiers de son processus d'appel. Ainsi, la bonne façon de gérer cela est de

  • près chaque descripteur spécial,
  • les rouvrir, pointant votre fichier journal désiré et
  • enfin appeler exec() pour démarrer le programme.

Réécriture votre code exemple ci-dessus, cela fonctionne très bien:

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

... ou, en utilisant la syntaxe indirecte objet recommandé par perldoc:

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

(en fait, selon à la documentation, cette syntaxe finale est le seul moyen fiable d'éviter l'instanciation d'un shell dans Windows.)

4

La commande shell shell indique au shell de lancer /bin/ls avec /etc pour l'argument avec son STDOUT redirigé.

/bin/ls /etc >/tmp/stdout 

D'autre part, l'instruction Perl suivante indique au Perl pour remplacer le programme actuel avec /bin/ls, avec /etc et >/tmp/stdout pour les arguments.

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

Vous n'avez pas dit à Perl de rediriger STDOUT! Souvenez-vous que exec ne démarre pas un nouveau processus, donc si vous modifiez le fd 1 du processus enfant, il aura pour effet ls de s'exécuter dans le même processus.

Mais plutôt que de simplement corriger ce problème un (comme Greg Kennedy a fait), mais laisser vos autres problèmes intacts (par exemple l'incapacité de fausses déclarations lancement ls comme une erreur de ls), je vais vous montrer les corriger tous:

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); 
} 

Alors que cela vous a sauvé une centaine de lignes de code, open3 est encore un très bas niveau. (Vous rencontrerez des problèmes si vous devez faire face à deux tuyaux.) Je recommande plutôt IPC::Run3 (plus simple) ou IPC::Run (plus flexible).

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

ou

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