2012-05-26 16 views
13

Soy un novato de Perl, así que disculpe esta pregunta básica. Necesito modificar un programa existente de Perl. Quiero canalizar una cadena (que puede contener varias líneas) a través de un programa externo y leer el resultado de este programa. Entonces este programa externo se usa para modificar la cadena. Simplemente usemos cat como un programa de filtro. Lo intenté así, pero no funciona. (Salida de cat va a la salida estándar en lugar de ser leído por perl.)¿Cómo leer y escribir desde una tubería en 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"; 

He leído que este no es compatible con Perl, ya que puede terminar en un punto muerto y puedo entenderlo. ¿Pero cómo lo hago entonces?

+0

El 'perlipc' página del manual tiene una discusión y un montón de ejemplos de diferentes enfoques. – tripleee

Respuesta

22

Puede usar IPC::Open3 para lograr la comunicación bidireccional con niños.

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"; 
} 
+0

Funciona a la perfección. Gracias. Usé open2 en lugar de open3 porque no me importa stderr. – kayahr

+2

¡Y vale la pena señalar que los primeros dos argumentos para 'open2' son al revés a' open3'! Es 'open2 (\ * CHLD_OUT, \ * CHLD_IN, 'cat')' por ejemplo. –

8

Esto implica la programación del sistema, por lo que es más que una cuestión básica. Tal como está escrito, su programa principal no requiere interacción de dúplex completo con el programa externo. Flujo de datos se desplaza en una dirección, es decir,

cadena → → programa externo programa principal

La creación de este gasoducto es sencillo. El open de Perl tiene un modo útil explicado en el “Safe pipe opens” section of the perlipc documentation.

Otro enfoque interesante para la comunicación entre procesos es hacer que su único programa tenga múltiples procesos y se comunique entre o incluso entre ustedes mismos. La función open aceptará un argumento de archivo de "-|" o "|-" para hacer algo muy interesante: bifurca a un niño conectado al identificador de archivo que ha abierto. El niño ejecuta el mismo programa que el padre. Esto es útil para abrir de manera segura un archivo cuando se ejecuta bajo un UID o GID supuesto, por ejemplo. Si abres un tubo a menos, puedes escribir en el identificador de archivo que abriste y tu hijo lo encontrará en su STDIN. Si abre una tubería desde el signo menos, puede leer en el identificador de archivo que abrió lo que su hijo escribe en su STDOUT.

Esto es un open que implica una tubería, que da matices al valor de retorno. El perlfunc documentation on open explica.

Si abre un tubo en el comando - (es decir, especifique |- o -| con los de una o dos formas de argumento open), una implícita fork se realiza, por lo vuelve abiertas dos veces: en el proceso primario devuelve el pid del proceso hijo, y en el proceso hijo devuelve (un definido) 0. Use defined($pid) o // para determinar si el open fue exitoso.

Para crear el andamiaje, trabajamos de derecha a izquierda usando open a fork un nuevo proceso en cada paso.

  1. Su programa principal ya se está ejecutando.
  2. A continuación, fork un proceso que eventualmente se convertirá en el programa externo.
  3. Dentro del proceso desde el paso 2
    1. Primera fork el proceso de la cadena de impresión a fin de hacer llegar su producción en nuestro STDIN.
    2. Luego exec el programa externo para realizar su transformación.
  4. Haz que la impresora de cuerdas haga su trabajo y luego exit, que avanza al siguiente nivel.
  5. De vuelta en el programa principal, lea el resultado transformado.

Con todo eso configurado, todo lo que tiene que hacer es implantar su sugerencia en la parte inferior, Sr. 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: $!"; 
} 

Gracias por la oportunidad de escribir un programa tan divertido!

2

Puede utilizar el interruptor de línea de comandos -n para envolver eficazmente su código de programa existente en un bucle while ... mirar la página del manual de -n:

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

continuación, puede utilizar el sistema operativo mecanismo de tubería directamente

cat file | your_perl_prog.pl 

(Editar) voy a tratar de explicar esto con más cuidado ...

la pregunta no es clara acerca de qué parte del p programa erl juega: filtro o etapa final. Esto funciona en cualquier caso, entonces asumiré que es el último.

'your_perl_prog.pl' es su código actual. Llamaré a su filtro de programa 'filtro'.

Modificar your_perl_prog.pl modo que la línea shebang ha añadido un '-n' interruptor: #!/Usr/bin/perl -n o #/bin/env "Perl -n"

Esto efectivamente pone un tiempo (<>) {} lazo alrededor del código en your_perl_prog.pl

añadir un bloque BEGIN para imprimir el encabezado:

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

puede leer cada línea con el proceso de '$line = <>;' y/impresión

continuación, se invoca el lote con

cat sourcefile |filter|your_perl_prog.pl 
+0

Explique el voto abajo para que pueda defender la respuesta – Rondo

+0

Agregué una edición para explicar lo que quise decir – Rondo

1

Quiero ampliar sobre la respuesta de @ Greg tocino sin cambiarlo.

tuve que ejecutar algo similar, pero querían código sin las órdenes dadas/cuándo, y también encontró que había explícito de salida() pierda ninguna llamada, ya que en el código de ejemplo que no se concretó y se cierra.

También tuve que hacerlo funcionar también en una versión que ejecuta ActiveState perl, pero esa versión de perl no funciona.Ver esta pregunta How to read to and write from a pipe in perl with 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: $!"; 
Cuestiones relacionadas