web-dev-qa-db-fra.com

Comment lire et écrire à partir d'une pipe en Perl?

Je suis un perl noob, veuillez donc excuser cette question de base. J'ai besoin de modifier un programme Perl existant. Je veux diriger une chaîne (qui peut contenir plusieurs lignes) via un programme externe et lire la sortie de ce programme. Donc, ce programme externe est utilisé pour modifier la chaîne. Utilisons simplement cat comme programme de filtrage. Je l'ai essayé comme ça mais ça ne marche pas. (La sortie de cat va à stdout au lieu d'être lue par Perl.)

#!/usr/bin/Perl

open(MESSAGE, "| cat |") or die("cat failed\n");
print MESSAGE "Line 1\nLine 2\n";
my $message = "";
while (<MESSAGE>)
{
    $message .= $_;
}
close(MESSAGE);
print "This is the message: $message\n";

J'ai lu que cela n'est pas pris en charge par Perl car cela peut se retrouver dans une impasse et je peux le comprendre. Mais comment dois-je faire alors?

17
kayahr

Vous pouvez utiliser IPC :: Open3 pour établir une communication bidirectionnelle avec l'enfant.

use strict;
use IPC::Open3;

my $pid = open3(\*CHLD_IN, \*CHLD_OUT, \*CHLD_ERR, 'cat')
    or die "open3() failed $!";

my $r;

for(my $i=1;$i<10;$i++) {
    print CHLD_IN "$i\n";
    $r = <CHLD_OUT>;
    print "Got $r from child\n";
}
26
tuxuday

Cela implique une programmation système, c'est donc plus qu'une question de base. Tel qu'il est écrit, votre programme principal ne nécessite pas d'interaction en duplex intégral avec le programme externe. Le flux de données se déplace dans une seule direction, à savoir

chaîne → programme externe → programme principal

La création de ce pipeline est simple. open de Perl a un mode utile expliqué dans la section "Ouverture sécurisée du tuyau" de la documentation de perlipc .

Une autre approche intéressante de la communication interprocessus consiste à rendre votre programme unique multiprocessus et à communiquer entre - ou même entre - vous-mêmes. La fonction open acceptera un argument de fichier de "-|" Ou "|-" Pour faire quelque chose de très intéressant: cela entraînera un enfant connecté au descripteur de fichier que vous avez ouvert. L'enfant exécute le même programme que le parent. Cela est utile pour ouvrir un fichier en toute sécurité lors de l'exécution sous un UID ou GID supposé, par exemple. Si vous ouvrez un canal vers moins, vous pouvez écrire dans le descripteur de fichier que vous avez ouvert et votre enfant le trouvera dans son STDIN. Si vous ouvrez un tube depuis moins, vous pouvez lire à partir du descripteur de fichier que vous avez ouvert tout ce que votre enfant écrit dans son STDOUT.

Il s'agit d'un open qui implique un tube, ce qui donne des nuances à la valeur de retour. La documentation perlfunc sur open explique.

Si vous ouvrez un canal sur la commande - (C'est-à-dire, spécifiez |- Ou -| Avec les formes à un ou deux arguments de open) , un fork implicite est fait, donc open renvoie deux fois: dans le processus parent, il renvoie le pid du processus enfant, et dans le processus enfant, il retourne (un défini) 0. Utilisez defined($pid) ou // Pour déterminer si open a réussi.

Pour créer l'échafaudage, nous travaillons de droite à gauche en utilisant open à fork un nouveau processus à chaque étape.

  1. Votre programme principal est déjà en cours d'exécution.
  2. Ensuite, fork un processus qui deviendra éventuellement le programme externe.
  3. À l'intérieur du processus de l'étape 2
    1. D'abord fork le processus d'impression de chaîne afin de faire arriver sa sortie sur notre STDIN.
    2. Puis exec le programme externe pour effectuer sa transformation.
  4. Demandez à l'imprimante de chaîne de faire son travail, puis à exit, qui passe au niveau suivant.
  5. De retour dans le programme principal, lisez le résultat transformé.

Avec tout cela mis en place, tout ce que vous avez à faire est d'implanter votre suggestion au fond, M. Cobb.

#! /usr/bin/env Perl

use 5.10.0;  # for defined-or and given/when
use strict;
use warnings;

my @transform = qw( tr [A-Za-z] [N-ZA-Mn-za-m] );  # rot13
my @inception = (
  "V xabj, Qnq. Lbh jrer qvfnccbvagrq gung V pbhyqa'g or lbh.",
  "V jnf qvfnccbvagrq gung lbh gevrq.",
);

sub snow_fortress { print map "$_\n", @inception }

sub hotel {
  given (open(STDIN, "-|") // die "$0: fork: $!") {  # / StackOverflow hiliter
    snow_fortress when 0;
    exec @transform or die "$0: exec: $!";
  }
}

given (open(my $fh, "-|") // die "$0: fork: $!") {
  hotel when 0;

  print while <$fh>;
  close $fh or warn "$0: close: $!";
}

Merci pour l'opportunité d'écrire un programme aussi amusant!

10
Greg Bacon

Vous pouvez utiliser le commutateur de ligne de commande -n pour encapsuler efficacement votre code de programme existant dans une boucle while ... regardez la page de manuel pour -n:

 LINE:
            while (<>) {
                ...             # your program goes here
            }

Ensuite, vous pouvez utiliser directement le mécanisme de tuyau du système d'exploitation

cat file | your_Perl_prog.pl

(Edit) Je vais essayer d'expliquer cela plus attentivement ...

La question n'est pas claire de savoir quel rôle joue le programme Perl: filtre ou étape finale. Cela fonctionne dans les deux cas, donc je suppose que c'est le dernier.

'your_Perl_prog.pl' est votre code existant. J'appellerai votre programme de filtrage "filtre".

Modifiez your_Perl_prog.pl pour que la ligne Shebang ait un commutateur '-n' ajouté: #!/Usr/bin/Perl -n ou #!/Bin/env "Perl -n"

Cela met effectivement une boucle while (<>) {} autour du code dans your_Perl_prog.pl

ajoutez un bloc BEGIN pour imprimer l'en-tête:

BEGIN {print "HEADER LINE\n");}

Vous pouvez lire chaque ligne avec '$line = <>;' et traiter/imprimer

Appelez ensuite le lot avec

cat sourcefile |filter|your_Perl_prog.pl
2
Rondo

Je veux développer la réponse de @Greg Bacon sans la changer.

J'ai dû exécuter quelque chose de similaire, mais je voulais coder sans les commandes given/when, et j'ai également constaté qu'il manquait des appels explicites exit () parce que dans l'exemple de code, il était tombé à travers et s'était arrêté.

J'ai également dû le faire fonctionner également sur une version exécutant ActiveState Perl, mais cette version de Perl ne fonctionne pas. Voir cette question Comment lire et écrire depuis un tube en Perl avec ActiveState Perl?

#! /usr/bin/env Perl

use strict;
use warnings;

my $isActiveStatePerl = defined(&Win32::BuildNumber);

sub pipeFromFork
{
    return open($_[0], "-|") if (!$isActiveStatePerl);
    die "active state Perl cannot cope with dup file handles after fork";

    pipe $_[0], my $child or die "cannot create pipe";
    my $pid = fork();
    die "fork failed: $!" unless defined $pid;
    if ($pid) {         # parent
        close $child; 
    } else {            # child 
        open(STDOUT, ">&=", $child) or die "cannot clone child to STDOUT";
        close $_[0];
    }
    return $pid;
}


my @transform = qw( tr [A-Za-z] [N-ZA-Mn-za-m] );  # rot13
my @inception = (
  "V xabj, Qnq. Lbh jrer qvfnccbvagrq gung V pbhyqa'g or lbh.",
  "V jnf qvfnccbvagrq gung lbh gevrq.",
);

sub snow_fortress { print map "$_\n", @inception }

sub hotel 
{
    my $fh;
    my $pid = pipeFromFork($fh);     # my $pid = open STDIN, "-|";
    defined($pid)  or die "$0: fork: $!";
    if (0 == $pid) {
        snow_fortress;
        exit(0);
    }
    open(STDIN, "<&", $fh)  or  die "cannot clone to STDIN";
    exec @transform or die "$0: exec: $!";
}

my $fh;
my $pid = pipeFromFork($fh);            # my $pid = open my $fh, "-|";
defined($pid) or die "$0: fork: $!";
if (0 == $pid) {
    hotel;
    exit(0);
}

print while <$fh>;
close $fh or warn "$0: close: $!";
1
codeDr