2010-08-13 5 views
2

J'essaie d'exécuter bp_genbank2gff3.pl (paquet bioperl) à partir d'un autre script perl que obtient un genbank comme argument.Problème avec le handle de fichier canalisé en perl

Cela ne fonctionne pas (pas de fichiers de sortie sont générés):

my $command = "bp_genbank2gff3.pl -y -o /tmp $ARGV[0]"; 

    open(my $command_out, "-|", $command); 
    close $command_out; 

mais cela ne

open(my $command_out, "-|", $command); 
    sleep 3; # why do I need to sleep? 
    close $command_out; 

Pourquoi?

Je pensais que close est censé bloquer jusqu'à ce que la commande se fait:

La fermeture d'un descripteur de fichier provoque canalisé le processus parent d'attendre l'enfant pour terminer ... (voir http://perldoc.perl.org/functions/open.html).

Modifier

J'ajouté ce dernier en ligne:

say "ret=$ret, \$?=$?, \$!=$!"; 

et dans les deux cas, l'impression est:

ret=, $?=13, $!= 

(ce qui signifie close a échoué dans les deux cas , à droite?)

+1

Quelle est la valeur de retour de 'close()'? Qu'est-ce que "$?" Et peut-être "$!"? Est-ce que 'bp_genbank2gff3.pl', ou l'expansion shell de' $ ARGV [0] 'fork et exit? Que dit «Strace» ou «Truss»? Êtes-vous sûr que les fichiers de sortie dans le cas de "travail" ne sont pas laissés par un travail non lié et réussi? Pouvez-vous reproduire le comportement problématique avec un utilitaire shell communément disponible, plutôt que 'bp _... 3.pl'? – pilcrow

+0

@pilcrow voir éditer. 'strace' renvoie une très longue liste, que dois-je rechercher? Je suis sûr que la sortie n'est pas un résidu quand 'sleep' est activé (je supprime tout le contenu du répertoire avant de l'exécuter). Je n'ai pas compris votre question sur la fourche. BTW: http://github.com/bioperl/bioperl-live/blob/master/scripts/Bio-DB-GFF/genbank2gff3.PLS –

+1

'strace -fe trace = processus mon_perl_script' devrait vous aider à démarrer. Cependant, @mobrule l'a trouvé à partir de $? – pilcrow

Répondre

5

$? = 13 signifie que votre processus fils a pris fin par un signal SIGPIPE. Votre programme externe (bp_genbank2gff3.pl) a essayé d'écrire une sortie vers un canal vers votre programme perl. Mais le programme perl a fermé son extrémité du canal afin que votre système d'exploitation envoie un SIGPIPE au programme externe.

Par sleep e pendant 3 secondes, vous laissez votre programme s'exécuter pendant 3 secondes avant que le système d'exploitation ne le tue, ce qui permet à votre programme de faire quelque chose. Notez que les tuyaux ont une capacité limitée, donc si votre script parent ne lit pas depuis le canal et si le programme externe écrit beaucoup sur la sortie standard, les opérations d'écriture du programme externe finissent par bloquer et vous ne pouvez pas vraiment obtenir 3 secondes d'effort de votre programme externe.

La solution de contournement consiste à lire la sortie du programme externe, même si vous allez simplement la jeter.

open(my $command_out, "-|", $command); 
my @ignore_me = <$command_out>; 
close $command_out; 


Mise à jour: Si vraiment vous ne se soucient pas de la sortie de la commande, vous pouvez éviter SIGPIPE problèmes en redirigeant la sortie vers /dev/null:

open my $command_out, "-|", "$command > /dev/null"; 
close $command_out;  # succeeds, no SIGPIPE 

Bien sûr, si vous allez aller à tant de mal à ignorer la sortie, vous pouvez aussi bien utiliser system.


Informations complémentaires: Comme l'OP dit, la fermeture d'un descripteur de fichier fait que le parent canalisé d'attendre l'enfant à terminer (en utilisant waitpid ou quelque chose de similaire). Mais avant il commence à attendre, il ferme son extrémité du tuyau. Dans ce cas, cette extrémité est l'extrémité de lecture du canal vers lequel le processus enfant écrit sa sortie standard. La prochaine fois que l'enfant essaie d'écrire quelque chose sur la sortie standard, le système d'exploitation détecte que l'extrémité de lecture de ce canal est fermée et envoie un SIGPIPE au processus fils, le détruisant et laissant rapidement l'instruction close dans le parent.

+0

Je ne comprends pas quelque chose. Mon programme externe a en effet essayé d'écrire une sortie (quelque chose comme "travailler sur ..." au début puis "terminé" à la fin). Mais pourquoi voulez-vous dire par "le programme Perl a fermé son bout de la pipe"? En d'autres termes, pourquoi exactement mon @ignore_me = <$ command_out>; 'cela fait-il une telle différence? –

+2

@David B - Vous obtenez un 'SIGPIPE' quand un processus (votre enfant, dans cet exemple) écrit sur un tube que le lecteur (votre parent) a déjà fermé. En écrivant '<$ command_out>' dans le parent, vous maintenez l'extrémité de lecture du tuyau ouverte jusqu'à la fin de l'écriture du tuyau. – mob

+2

Il lit ce que l'enfant essaie d'écrire. Cela fait une différence. Vous tournez l'eau vers le tuyau d'arrosage et colmatez la pointe du tuyau en ne lisant pas la sortie du programme auquel vous avez ouvert un tuyau. Si vous ne voulez pas la sortie du programme, utilisez 'system'. –

0

Je ne suis pas sûr de ce que vous essayez de faire, mais system est probablement mieux dans ce cas ...

+0

J'utilise 'open' car je voudrais lire la commande stdout à la volée, ce qui ne peut pas être fait en utilisant' system' autant que je sache ('system' attend la fin de la commande puis retourne toute la sortie à une fois que). L'exemple ci-dessus est juste une version simplifiée qui n'inclut pas cette partie, mais elle est sans importance pour le problème. –

+0

Ok, vous avez donc besoin d'un "while (<$ command_out>) {do stuff}" entre votre ouverture et fermeture – sebthebert

+0

Ceci est une méthode générique que j'utilise. Donc parfois je veux vraiment quelque chose avec ce que la commande envoie à stdout, et puis j'utilise vraiment le 'while' comme vous l'avez suggéré. Mais si je ne le fais pas, pourquoi la commande ne devrait-elle pas s'exécuter correctement? La commande que nous discutons ici, par exemple, obtient un nom de fichier de format puis divise ses données en deux fichiers de formats différents. Il génère des fichiers et imprime également des messages de journalisation sur stdout. Dans ce cas, je ne me soucie pas de la sortie standard, donc je n'utilise pas un moment. Pourquoi cela devrait-il être important? –

Questions connexes