perl - strawberry - ¿Cuándo es el momento correcto(y el momento equivocado) para utilizar los puntos de apoyo?
perl tutorial (14)
Muchos programadores principiantes escriben código como este:
sub copy_file ($$) {
my $from = shift;
my $to = shift;
`cp $from $to`;
}
¿Esto es malo, y por qué? ¿Alguna vez se deben usar los puntos de apoyo? ¿Si es así, cómo?
De la página de manual de "perlop":
Eso no significa que debas salirte de tu camino para evitar los tiros largos cuando son la manera correcta de hacer algo. Perl fue creado para ser un lenguaje pegajoso, y una de las cosas que combina es el comando. Solo entiende en lo que te estás metiendo.
En Perl, siempre hay más de una forma de hacer lo que quieras. El punto principal de los backticks es obtener la salida estándar del comando de shell en una variable de Perl. (En su ejemplo, todo lo que imprime el comando cp se devolverá a la persona que llama.) La desventaja de usar los trazos atrás en su ejemplo es que no verifica el valor de retorno del comando de shell; cp podría fallar y no lo notarías. Puedes usar esto con la variable especial Perl $ ?. Cuando quiero ejecutar un comando de shell, tiendo a usar el sistema :
system("cp $from $to") == 0
or die "Unable to copy $from to $to!";
(También observe que esto fallará en los nombres de archivos con espacios integrados, pero supongo que ese no es el punto de la pregunta).
Aquí hay un ejemplo artificial de donde los backticks pueden ser útiles:
my $user = `whoami`;
chomp $user;
print "Hello, $user!/n";
Para casos más complicados, también puede usar abrir como una tubería:
open WHO, "who|"
or die "who failed";
while(<WHO>) {
# Do something with each line
}
close WHO;
En general, es mejor usar el sistema en lugar de los backticks porque:
sistema alienta a la persona que llama a verificar el código de retorno del comando.
el sistema permite la notación de "objetos indirectos", que es más segura y agrega flexibilidad.
Backticks están culturalmente vinculados a las secuencias de comandos shell, que pueden no ser comunes entre los lectores del código.
Los Backticks usan una sintaxis mínima para lo que puede ser un comando pesado.
Una razón por la que los usuarios podrían estar acostumbrados a utilizar patillas en lugar de sistemas es ocultar STDOUT del usuario. Esto se logra más fácil y flexible redirigiendo la secuencia STDOUT:
my $cmd = ''command > /dev/null'';
system($cmd) == 0 or die "system $cmd failed: $?"
Además, deshacerse de STDERR se logra fácilmente:
my $cmd = ''command 2> error_file.txt > /dev/null'';
En situaciones en las que tiene sentido usar palos de fondo, prefiero usar qx {} para enfatizar que se está produciendo un comando pesado.
Por otro lado, tener otra manera de hacerlo realmente puede ayudar. Algunas veces solo necesita ver lo que un comando imprime en STDOUT. Los Backticks, cuando se utilizan como scripts de shell, son la herramienta adecuada para el trabajo.
Haga lo que haga, además de desinfectar las entradas y verificar el valor de retorno de su código, asegúrese de llamar a cualquier programa externo con su ruta explícita y completa. por ejemplo decir
my $user = `/bin/whoami`;
o
my $result = `/bin/cp $from $to`;
Decir simplemente "whoami" o "cp" corre el riesgo de ejecutar accidentalmente un comando diferente de lo que pretendía, si la ruta del usuario cambia, que es una vulnerabilidad de seguridad que un atacante malintencionado podría intentar explotar.
La regla es simple: nunca use los backticks si puede encontrar un built-in para hacer el mismo trabajo, o si es un módulo robusto en el CPAN que lo hará por usted. Los Backticks a menudo se basan en un código poco práctico e incluso si no identifica las variables, aún puede abrirse a muchos agujeros de seguridad.
Nunca use backticks con datos de usuario a menos que haya especificado lo que está permitido (no lo que está prohibido, ¡se perderá cosas)! Esto es muy, muy peligroso.
Los Backticks deben usarse solo si necesita capturar la salida de un comando. De lo contrario, se debería usar system (). Y, por supuesto, si hay una función de Perl o un módulo de CPAN que hace el trabajo, esto debería usarse en lugar de cualquiera.
En cualquier caso, se recomiendan dos cosas:
Primero, desinfecte todas las entradas: use el modo Taint (-T) si el código está expuesto a una posible entrada no confiable. Incluso si no es así, asegúrese de manejar (o evitar) caracteres funky como el espacio o los tres tipos de comillas.
Segundo, verifique el código de retorno para asegurarse de que el comando tuvo éxito. Aquí hay un ejemplo de cómo hacerlo:
my $cmd = "./do_something.sh foo bar";
my $output = `$cmd`;
if ($?) {
die "Error running [$cmd]";
}
Otra forma de capturar stdout (además de pid y código de salida) es usar IPC :: Open3 que puede negar el uso tanto del sistema como de los backticks.
Para el caso que muestra utilizando el módulo Archivo :: Copiar es probablemente el mejor. Sin embargo, para responder a su pregunta, cada vez que necesito ejecutar un comando del sistema, generalmente confío en IPC :: Run3 . Proporciona una gran cantidad de funcionalidades como la recopilación del código de retorno y la salida estándar y de error.
Perl tiene una personalidad dividida. Por un lado, es un excelente lenguaje de scripting que puede reemplazar el uso de un shell. En este tipo de uso único de I-watching-the-outcome, los backticks son convenientes.
Cuando se usa un lenguaje de programación, se deben evitar los backticks. Esto es una falta de comprobación de errores y, si se pueden evitar los backticks del programa por separado, se puede ganar eficiencia.
Además de lo anterior, la función del sistema debe usarse cuando la salida del comando no se está utilizando.
Solo se supone que los Backticks se usan cuando quieres capturar resultados. Usarlos aquí "parece tonto". Le indicará a cualquiera que mire su código que no está muy familiarizado con Perl.
Usa los backticks si quieres capturar la salida. Usa el sistema si quieres ejecutar un comando. Una ventaja que obtendrá es la capacidad de verificar el estado de devolución. Use módulos donde sea posible para la portabilidad. En este caso, File :: Copy se ajusta a la factura.
Su ejemplo es malo porque hay instrucciones integradas de Perl para hacer lo que son portátiles y, por lo general, más eficientes que la alternativa de retroceso.
Deben usarse solo cuando no hay una alternativa incorporada de Perl (o módulo). Esto es tanto para backticks como para llamadas al sistema (). Los Backticks están destinados a capturar la salida del comando ejecutado.
Algunas personas ya han mencionado que solo debes usar los apoyos cuando:
- Necesita capturar (o suprimir) la salida.
- No existe una función incorporada o módulo Perl para hacer la misma tarea, o tiene una buena razón para no usar el módulo o incorporado.
- Usted desinfecta su entrada.
- Usted verifica el valor de retorno.
Desafortunadamente, cosas como verificar el valor de retorno correctamente pueden ser bastante desafiantes. ¿Murió a una señal? ¿Funcionó hasta su finalización, pero devuelve un estado de salida divertido? ¿Las formas estándar de tratar de interpretar $?
son horribles
Recomiendo usar las funciones de capture()
y de system()
del IPC :: System :: Simple en lugar de las barras de retroceso. La función capture()
funciona igual que los backticks, excepto que:
- Proporciona diagnósticos detallados si el comando no se inicia, es eliminado por una señal o devuelve un valor de salida inesperado.
- Proporciona diagnósticos detallados si se pasan datos contaminados.
- Proporciona un mecanismo fácil para especificar valores de salida aceptables.
- Le permite llamar a los backticks sin el shell, si lo desea.
- Proporciona mecanismos confiables para evitar el shell, incluso si usa un único argumento.
Los comandos también funcionan de manera consistente en todos los sistemas operativos y versiones de Perl, a diferencia del system()
integrado de Perl system()
que puede no detectar datos viciados cuando se invocan con múltiples argumentos en versiones anteriores de Perl (por ejemplo, 5.6.0 con argumentos múltiples) o puede llamar al shell de todos modos en Windows.
Como ejemplo, el siguiente fragmento de código guardará los resultados de una llamada a perldoc
en un escalar, evitará el shell y arrojará una excepción si no se puede encontrar la página (ya que perldoc devuelve 1).
#!/usr/bin/perl -w
use strict;
use IPC::System::Simple qw(capture);
# Make sure we''re called with command-line arguments.
@ARGV or die "Usage: $0 arguments/n";
my $documentation = capture(''perldoc'', @ARGV);
IPC :: System :: Simple es Perl puro, funciona en 5.6.0 y superior, y no tiene ninguna dependencia que normalmente no vendría con su distribución de Perl. (En Windows depende de un Win32 :: módulo que viene con ActiveState y Strawberry Perl).
Descargo de responsabilidad: soy el autor de IPC :: System :: Simple , por lo que puedo mostrar algunos prejuicios.
Backticks son para aficionados. La solución a prueba de balas es un "Safe Pipe Open" (vea "hombre perlipc"). Ejecuta su comando en otro proceso, lo que le permite usar primero STDERR, setuid, etc. Ventajas: no se basa en el shell para analizar @ARGV, a diferencia de open ("$ cmd $ args |"), que no es confiable . Puede redirigir STDERR y cambiar los privilegios del usuario sin cambiar el comportamiento de su programa principal. Esto es más detallado que los backticks, pero puede envolverlo en su propia función como run_cmd ($ cmd, @ args);
sub run_cmd {
my $cmd = shift @_;
my @args = @_;
my $fh; # file handle
my $pid = open($fh, ''-|'');
defined($pid) or die "Could not fork";
if ($pid == 0) {
open STDERR, ''>/dev/null'';
# setuid() if necessary
exec ($cmd, @args) or exit 1;
}
wait; # may want to time out here?
if ($? >> 8) { die "Error running $cmd: [$?]"; }
while (<$fh>) {
# Have fun with the output of $cmd
}
close $fh;
}
Usa los backticks cuando quieras recolectar el resultado del comando.
De lo contrario, system()
es una mejor opción, especialmente si no necesita invocar un shell para manejar metacaracteres o análisis de comandos. Puede evitar eso pasando una lista a system (), por ejemplo, system(''cp'', ''foo'', ''bar'')
(sin embargo, probablemente sea mejor usar un módulo para ese ejemplo en particular :))