Bash

También podría gustarte

Está en la página 1de 2

019-020_Bash

29.03.2006

10:13

Uhr

Pgina

19

Programacin Bash PORTADA

Perfeccionando los scripts en Bash

PROGRAMANDO SCRIPTS
En los viejos tiempos, las shells tan slo eran capaces de realizar llamadas externas a programas y ejecutar algunos comandos bsicos, los comandos internos. Sin embargo, con todas las novedades aadidas a la ltima versin de Bash, rara vez har falta el apoyo de los programas externos. POR MIRKO DLLE

uchos scripts tratan con Bash como si slo fuera capaz de poco ms que llamar a programas externos. Esto es sorprendente, desde la versin 2 de la shell por defecto hay disponible un conjunto de comandos que abarcan de todo, desde la manipulacin compleja de las cadenas de caracteres, pasando por las expresiones regulares, hasta los arrays; lo que hace que sean redundantes las llamadas a los programas externos. La principal ventaja de las funciones internas es que la shell no tiene que lanzar un proceso nuevo, lo que ahorra tiempo de proceso y memoria. Esta capacidad puede ser importante, particularmente si hace falta ejecutar un programa como grep o cut en un bucle, ya que el consumo de tiempo y memoria del script puede dispararse si no se tiene cuidado. Este artculo describe algunas tcnicas simples para acelerar los scripts Bash.

En el Banco de Pruebas
Los siguientes scripts evalan un fichero de registro de Apache de un sitio web para realizar una comparativa. Si est interesado en conocer qu pginas han sido solicitadas, hay que aislar la cadena GET del fichero de registro, como la siguiente:
84.57.16.30 - - U [21/Oct/2005:04:18:26 +0200] U "GET /favicon.ico HTTP/1.1" U 404 209 "-" "Mozilla/5.0 U (X11; U; Linux i686; de-DE; U rv:1.7.5) Gecko/20041122 U Firefox/1.0"

El Listado 1 muestra una solucin que utilizan muchos scripts Bash. La llamada a cat en la Lnea 3 lee primero el fichero de registro entero, y trabaja con l en el bucle for como si se tratara de una lista de parmetros, con lo que Bash tiene

que mantener en la cach el contenido del primer fichero. En la Lnea 5, Listado 1, se contina realizando una llamada al programa externo cut por cada lnea del fichero de registro. La ejecucin del script en una mquina Pentium III a 750Mhz tard 18,5 segundos en el anlisis de un fichero de registro de 600Kbyte de Apache. Por otro lado, el script del Listado 2 tan slo tard 3.3 segundos, casi seis veces ms rpido: utiliza el descriptor de ficheros 3 para abrir el fichero y hace uso de la variable Request para procesar una lnea en cada vuelta del bucle, con lo que Bash tan slo necesitar mantener en la cach una nica lnea del fichero de registro. A continuacin, el script elimina los caracteres del comienzo de la lnea, hasta que aparece GET, incluido este, y todos los caracteres del final de la lnea hasta HTTP/, incluido.

Listado 3: Grep Interno Listado 1: Evaluacin Externa de los Registros


01 #!/bin/bash 02 IFS=$'\n' 03 for l in `cat access.log`; do 04 IFS=" " 05 echo "${l}" | cut -d" " -f7 06 done

Listado 2: Evaluacin Interna de los Registros


01 #!/bin/bash 02 exec 3<access.log 03 while read -u 3 Request; do 04 Request="${Request##*GET }" 05 echo "${Request%% HTTP/*}" 06 done

01 #!/bin/bash 02 exec 3<$2 03 while read -u 3 line; do 04 if [ -z "${line/*${1}*}" ]; then 05 echo "$line" 06 fi 07 done

WWW.LINUX- MAGAZINE.ES

Nmero 17

19

019-020_Bash

29.03.2006

10:13

Uhr

Pgina

20

PORTADA Programacin Bash

Listado 4: Funciones de Cadena de Caracteres


01 02 03 04 05 06 07 08 09 10 11 #!/bin/bash function GetIP() { while read -u $1 Request; do tmp="${Request%% *}" IP[1]="${tmp%%.*}" IP[4]="${tmp##*.}" tmp="${tmp%.*}" IP[3]="${tmp##*.}" tmp="${tmp%.*}" IP[2]="${tmp##*.}" printf "%03d.%03d.%03d.%03d\n" ${IP[1]} ${IP[2]} ${IP[3]} ${IP[4]} done } exec 3<access.log GetIP 3 | sort | uniq

12 13 14 15

Se puede ahorrar otra dcima de segundo eliminando todos los caracteres que van hasta el blanco del final de la peticin, ya que se simplifica la comparacin de la cadena por la funcin interna de Bash.

No tiene sentido reemplazar cada llamada a un programa con los comandos internos. El Listado 3 es un buen ejemplo de ello: implementa un grep rudimentario. Aunque el script comprende tan slo unas cuantas lneas de cdigo y podra parecer eficiente a simple vista, tarda unos dos minutos en buscar un nombre de fichero en los 600Kbytes del fichero de registro del servidor web (grep lo realiza en 0,1 segundos). Si se encuentra una coincidencia, la lnea analizada del fichero se borra completamente por una bsqueda y reemplazo en la Lnea 4. El patrn de bsqueda, *${1}*, le indica a Bash que busque cada carcter individual de la lnea en busca de una coincidencia. Si se utiliza #*${1}* como patrn de bsqueda, dicindole a Bash que busque al comienzo de la lnea nicamente, Bash solamente realiza la comparacin una vez por lnea reduciendo el tiempo del script a menos de tres segundos.

Listado 5: Funciones de Cadena de Caracteres


01 02 03 04 05 #!/bin/bash function GetIP() { IFS=". " while read -u $1 -a IP; do printf "%03d.%03d.%03d.%03d\n" ${IP[0]} ${IP[1]} ${IP[2]} ${IP[3]} done } exec 3<access.log GetIP 3 | sort | uniq

06 07 08 09

Funciones de Compactacin
El programa del Listado 5 realiza el mismo trabajo del Listado 4, pero tan slo tarda 1,6 segundos, una mejora de casi el 40 por ciento. Las funciones de cadena de la Lnea 4 a la 10 del Listado 4 son las que causan la diferencia en los tiempos de ejecucin: en vez de extraer primero las direcciones IP y luego utilizar siete llamadas a funciones para diseccionarlas, el Listado 5 llama a la funcin interna read de Bash con la variable especial IFS. Bash trata los caracteres almacenados en IFS como parmetros separadores. Por defecto son caracteres espacio, tabulador y lnea nueva. La Lnea 3 del Listado 5 define el punto y el espacio en blanco como separadores. Llamando a read con el parmetro -a se le indica a la funcin que no almacene una lnea completa en la variable, sino que haga uso del separador IFS y que escriba los elementos de la entrada uno a uno en la variable array IP. Los octetos que conforman la direccin IP se almacenan en las variables que van desde la IP[0] hasta la IP[3] de la llamada a read. Adems, una nica llamada a la funcin en el Listado 5 reemplaza las lneas de la 3 a la 10 del Listado 4. Se pueden reemplazar las llamadas externas a sort y uniq por funciones Bash, pero no se puede esperar que Bash sea capaz de igualar a sort, un programa C, en eficiencia. Como en la vida real, algunos ajustes en los scripts Bash pueden ser sorprendentes, pero programar en busca de la eficiencia tambin puede pagarse a la I larga.

Diseccionando IPs
Pero existe una forma ms elegante y rpida de diseccionar las cadenas en algunos escenarios. Por ejemplo, si hay que ordenar las direcciones IP por las que fueron originadas, no se puede utilizar la funcin sort o una simple funcin de ordenacin lxica. Ya que pondra a 217.83.13.152 antes que 62.104.118.59. Por el contrario, hace falta extraer los bytes individuales de la direccin IP, convertirlos a un formato ordenable, ordenarlos y por ltimo mostrar los resultados sin los duplicados. Los Listados 4 y 5 muestran dos soluciones posibles con caractersticas de rendimiento completamente diferentes. El script del Listado 5 comienza analizando el fichero de registro lnea por lnea (Lnea 3) y luego extrae la primera direccin IP en la Lnea 4. De la Lnea 6 a la 10 se elimina un octeto cada vez de la parte final de la direccin IP y se almacena el byte de la direccin como un decimal en el array IP. En la Lnea 11, la llamada a printf, que es tambin un comando interno de Bash, imprime los cuatro octetos de la direccin IP separados por puntos, tres dgitos decimales con relleno de ceros. La ltima lnea encauza la salida al programa externo sort, antes de eliminar los duplicados. El Listado 4 tarda unos 2,6 segundos en procesar el fichero de registro de 600KBytes de Apache.

Funciones de Sustitucin
Las herramientas basename y dirname son fcilmente sustituibles con las funciones Bash. Para realizar esta operacin se necesita la funcin de cadena ${Variable%Patrn}, que elimina la cadena ms corta que coincida con el patrn de la parte final de la cadena y ${Variable##Patrn}, que elimina la cadena ms larga que coincida con el patrn de la parte inicial de la cadena. Con un simple alias se reemplaza realmente dirname:
alias dirname=echo ${1%/*};

La funcin basename no es mucho ms compleja, aunque hay que considerar que basename puede eliminar la extensin del fichero, por ello hay que combinar las dos funciones de cadena:
function basename() { B=${1##*/} echo ${B%$2} }

Es ms eficiente almacenar los resultados de la cadena truncada en la variable B que establecer el primer parmetro y pasarle el segundo parmetro.

RECURSOS
[1] Programas de ejemplo: http://www. linux-magazine.com/Magazine/ Downloads/64/bash

20

Nmero 17

WWW.LINUX- MAGAZINE.ES

También podría gustarte