2012-07-17 20 views
21

Necesito ejecutar un comando de shell con system() en Perl. Por ejemplo,Capturar la salida del sistema Perl()

system('ls')

La llamada al sistema imprimirá en STDOUT, pero quiero capturar el resultado en una variable para que pueda realizar el procesamiento futuro con mi código Perl.

+1

No utilice el sistema, use abrir() con un tubo. –

+0

¿podría darme un ejemplo? ¡Gracias! – Dagang

+1

'perldoc -f open' –

Respuesta

32

Para eso sirven los palos de atrás. De perldoc perlfaq8:

Por qué no puedo obtener la salida de un comando con system()?

Está confundiendo el propósito de system() y los palos de atrás (``). system() ejecuta un comando y devuelve información de estado de salida (como un valor de 16 bits: los 7 bits bajos son la señal del proceso, de haberlos, y los 8 bits altos de son el valor de salida real). Backticks (``) ejecuta un comando y devuelve lo que envió a STDOUT.

my $exit_status = system("mail-users"); 
my $output_string = `ls`; 

Ver perldoc perlop para más detalles.

12

IPC::Run es mi módulo favorito para este tipo de tarea. Muy potente y flexible, y también trivialmente simple para casos pequeños.

use IPC::Run 'run'; 

run [ "command", "arguments", "here" ], ">", \my $stdout; 

# Now $stdout contains output 
+3

I He estado buscando una forma trivial de pasar argumentos de comando sin interpolación de shell, y capturar stdout del comando así llamado. Excelente respuesta, gracias! –

3

Simplemente uso similar para golpear ejemplo:

$variable=`some_command some args`; 

eso es todo. aviso, no verá en el resultado ninguna impresión en STDOUT porque esto se redirige a variable. Este ejemplo no se puede utilizar para el comando que interactúa con el usuario, excepto que tiene respuestas preparadas. para eso se puede usar algo como esto utilizando pila de comandos shell:

$variable=`cat answers.txt|some_command some args`; 

dentro answers.txt archivo que debe prepararse para todas las respuestas some_command funciona correctamente.

Sé que esta no es la mejor manera de programar :) pero esta es la manera más simple de cómo lograr el objetivo, especialmente para los programadores de bash.

Por supuesto, si la salida es mayor (ls con subdirectorio), no debería obtener todos los resultados a la vez. Comando de lectura de la misma manera a medida que lee el archivo regullar:

open CMD,'-|','your_command some args' or die [email protected]; 
my $line; 
while (defined($line=<CMD>)) { 
    print $line; #or push @table,$line or do whatewer what you want processing line by line 
} 
close CMD; 

solución extendida adicional para el procesamiento de salida de comando de tiempo sin extra de fiesta llamada:

my @CommandCall=qw(find/-type d); #some example single command 
my $commandSTDOUT; #file handler 
my $pid=open($commandSTDOUT),'-|'); #there will be implict fork! 
if ($pid) { 
    #parent side 
    my $singleLine; 
    while(defined($singleline=<$commandSTDOUT>)) { 
     chomp $line; #typically we don't need EOL 
     do_some_processing_with($line); 
    }; 
    close $commandSTDOUT; #in this place $? will be set for capture 
    $exitcode=$? >> 8; 
    do_something_with_exit_code($exitcode); 
} else { 
    #child side, there you really calls a command 
    open STDERR, '>>&', 'STDOUT'; #redirect stderr to stdout if needed, it works only for child, remember about fork 
    exec(@CommandCall); #at this point child code is overloaded by external command with parameters 
    die "Cannot call @CommandCall"; #error procedure if call will fail 
} 

Si utiliza el procedimiento de esa manera, se le capturar todo salida del procedimiento, y puede hacer todo el proceso línea por línea. Buena suerte :)

1

Quería ejecutar system() en lugar de backtacks porque quería ver la salida de rsync --progress. Sin embargo, también quería capturar la salida en caso de que algo salga mal según el valor de retorno. (Esto es para un script de respaldo).Esto es lo que estoy usando ahora:

use File::Temp qw(tempfile); 
use Term::ANSIColor qw(colored colorstrip); 



sub mysystem { 
    my $cmd = shift; #"rsync -avz --progress -h $fullfile $copyfile"; 
    my ($fh, $filename) = tempfile(); 
    # http://stackoverflow.com/a/6872163/2923406 
    # I want to have rsync progress output on the terminal AND capture it in case of error. 
    # Need to use pipefail because 'tee' would be the last cmd otherwise and hence $? would be wrong. 
    my @cmd = ("bash", "-c", "set -o pipefail && $cmd 2>&1 | tee $filename"); 
    my $ret = system(@cmd); 
    my $outerr = join('', <$fh>); 
    if ($ret != 0) { 
     logit(colored("ERROR: Could not execute command: $cmd", "red")); 
     logit(colored("ERROR: stdout+stderr = $outerr", "red")); 
     logit(colored("ERROR: \$? = $?, \$! = $!", "red")); 
    } 
    close $fh; 
    system("rm $filename"); 
    return $ret; 
} 

# and logit() is sth like: 
sub logit { 
    my $s = shift; 
    my ($logsec,$logmin,$loghour,$logmday,$logmon,$logyear,$logwday,$logyday,$logisdst)=localtime(time); 
    $logyear += 1900; 
    my $logtimestamp = sprintf("%4d-%02d-%02d %02d:%02d:%02d",$logyear,$logmon+1,$logmday,$loghour,$logmin,$logsec); 
    my $msg = "$logtimestamp $s\n"; 
    print $msg; 
    open LOG, ">>$LOGFILE"; 
    print LOG colorstrip($msg); 
    close LOG; 
} 
+0

¿Por qué llama al sistema ("rm $ filename")? tiene un procedimiento interno para desvincular $ filename, hace lo mismo sin llamadas adicionales sh o intérprete bash. – Znik

Cuestiones relacionadas