Peace of Code

Todos los días se aprende algo nuevo, dice el dicho. Pero esto se torna casi literal con ciertas cosas. En mi experiencia, VIM y Perl no dejan de sorprenderme. Como lo que nos interesa acá es extraer jugo de joroba de camélido, hablaremos de lo segundo.

El otro día nuestro querido Víctor me acercó un post en PerlMonks (lo conocen?) sobre one-liners, para darme ideas para la !PoC de este mes, y leyendo comentarios me enteré de algunas opciones de línea de comandos que harían encolerizar a todos los enemigos de Perl.

Esoterismo

Para empezar,

perl -pe1 archivo1 archivo2

"EH?" Me dirán. "Es otro reemplazo de cat!" Les diré.

Veamos en detalle. Estamos pasando las opciones -p y -e 1. La opción p tiene mucha magia detrás: encierra nuestro programa en un bloque de este tipo (info sacada de perlrun(1):

LINE:
 while (<>) {
   ...             # Acá va el programa
 } continue {
   print or die "-p destination: $!\n";
 }

Como resultado, cada línea de la entrada es impresa luego de pasar por nuestro programa, que no hace nada. De manera análoga, y siguiendo la sintáxis de sed, la opción -n itera sin imprimir por defecto. Poniendo esto alrededor de nuestro programa:

LINE:
 while (<>) {
   ...             # Acá va el programa
 }

Hasta hoy, yo hacía cosas como:

perl -e 'while(<>) { s/sasa/lala/gi; print }'

Pero es mucho mejor así:

perl -pe 's/sasa/lala/gi'

Travestismo

Y el camello de 2 toneladas sigue haciéndose pasar por el ratoncito sed, con la opción -i introducida recientemente en GNU sed, para hacer la edición in-place, es decir, no de standard input a standard output, sino dentro de cada archivo nombrado. De manera opcional nos permite decirle que haga un backup:

perl -i -pe 's/sasa/lala/gi' archivo
perl -i.bak -pe 's/sasa/lala/gi' archivo

Atención con no mezclar la opción -i con otras, ya que lo que esté a continuación se toma como la extensión a agregarle al archivo original para guardar un backup.

Para imprimir rangos, igual que en sed:

perl -ne 'print if /^DESDE$/ .. /^HASTA$/'

Para parecerce al viejo y querido awk (sabe usted qué significa el nombre awk?), tenemos la opción -a, autosplit, que nos deja en @F el contenido de cada línea cortado en los espacios en blanco, o lo que pongamos como argumento de la opción -F:

perl -lane 'print "$F[0] $F[3]"'
perl -F: -lane 'print "$F[0] $F[4]"' /etc/passwd

Otra interesante: -l, en combinación con -n o -p recorta los enter (como chomp) a la entrada y vuelve a ponerlos en la salida.

Magia negra

Aprovechándose de conocer exactamente el código que se agrega al usar la opción -n, hay gente que hace cosas como esta:

perl -lne'$x+=$_}{print$x'    # sum
perl -lne'$x+=$_}{print$x/$.' # avg

Claro que podríamos ser más elegantes y claros, y hacer lo que nos recomienda perlrun(1) y usar los bloques BEGIN y END, poco conocidos pero no tan esotéricos:

perl -lne '$x += $_; END { print $x }'

Noten la ausencia de \n en el print.

Este me gusta por simpático e inesperado, la gracia es darse cuénta de las opciones que estamos pasando. Además es útil, claro, como intérprete interactivo:

perl -demo

Más ejemplos

Uno que yo siempre hago en bash:

for i in *.JPG; do mv -i "$i" "${i/.JPG/.jpg}"; done

En perl puede hacerse así:

perl -le'foreach(<*.JPG>) {$o=$_;s/JPG$/jpg/; rename $o, $_ unless -e }'

Sí, yo también prefiero seguir haciéndolo en bash :)

W00t!

perl -i.bak -pe 's/Mozilla/Slopoke/g' /usr/bin/netscape

Palíndromos (palabras capicúa):

perl -lne 'print if $_ eq reverse' /usr/share/dict/spanish

Nota: hay código robado sin culpa del artículo de !PerlMonks y de la lista de one-liners de Tom Christiansen