Vous êtes sur la page 1sur 4

Python DESARROLLO

Un script pequeo y til

MAYORDOMO PYTHON
Mucha gente habla de Python como el sustituto normal de Perl, pero siempre lo vemos en grandes programas. Hoy vamos a demostrar lo fcil que es crear un script potente y sencillo en Python. POR JOS MARA RUIZ
uchas tareas cotidianas pueden automatizarse gracias a los lenguajes script. Siempre se ha dicho que Linux fomenta la creacin de pequeos script que ahorran grandes cantidades de tiempo. Pero con el paso de ste las actividades que se han desarrollado en los sistemas Linux han ido cambiando. Si antes la mayor parte del trabajo consista en gestionar centenares o miles de cuentas de usuarios individuales, actualmente existen cientos de directorios con miles de ficheros que se acumulan en nuestros equipos. Y siempre se desea guardarlos, pero es una tarea complicada, aburrida y montona. Adems hay que pensar! Dnde colocar los ficheros y cmo hacerlo? A continuacin se ver cmo crear un script Python que facilitar esta tarea: se pasar una ruta y una cantidad de espacio (por ejemplo el tamao de un DVD o CDROM, y l coger todos los ficheros de esa ruta y los guardar en directorios de manera que ninguno de ellos sobrepase el tamao que se les di. Uso? Pues el ms evidente, dar una primera idea de cmo poder

guardar toda esa informacin un primer paso para poder almacenar todos esos datos antes de que se tenga que lamentar.

Diseo del programa


Ser preciso pensar ahora en lo que se va a necesitar. Como se dispone de una ruta y de una cantidad de espacio expresado de alguna manera (digamos Megabytes), lo primero que habr que hacer es acceder a dicha ruta. Para ello se harn uso de las funciones de las libreras os y os.path. Una vez en la ruta deben recorrerse todos los directorios y subdirectorios que se encuentran en ella, registrando el

nombre de los ficheros y su tamao. Esto se har con una funcin que adems generar una estructura de datos para almacenar esa informacin. Con la estructura de datos ya en la mano se pasar a la toma de decisiones. De qu manera se van a guardar los datos en los directorios? Para ello se emplear otra funcin, que devolver otra estructura de datos en la que se almacenarn los distintos ficheros clasificados por directorios, con la condicin de que ninguno de esos directorios tenga un nmero tal de ficheros como para sobrepasar el lmite de tamao que se proporci. Esta es la parte dura. Con la nueva estructura de datos en la mano ya slo queda crear los directorios y mover los ficheros a ellos, y listo.

Recoleccin de los datos


Cmo se pueden recorrer todos los subdirectorios de un rbol de

WWW.LINUX- MAGAZINE.ES

Nmero 14

51

DESARROLLO Python

ficheros de la manera ms simple posible? El truco es emplear la recursividad. Una funcin es recursiva cuando se invoca a s misma dentro de su cdigo. Esta tcnica suele provocar dolores de cabeza, pero es ms sencilla de lo que pudiese aparentar en un primer momento. Slo tenemos que seguir dos reglas: que al llamar a la misma funcin de nuevo se le pasen menos datos que a la original. que exista una clusula IF que no ejecute la llamada a la misma funcin. Dicho as suena raro, por eso vamos a verlo en la prctica, ver Listado 1. La funcin recoge dos parmetros, ruta y hash. Como su nombre indica, se emplearn un hash o diccionario para almacenar el nombre y el tamao de los ficheros que se vayan encontrando. A travs de la funcin os.listdir() se consigue una lista con los ficheros y directorios que existen en la ruta que se pase. As que se recorre esa lista, pero las cadenas de la lista slo contienen el nombre de los ficheros y directorios, no

Listado 2: ejecucin recursiva


- se ejecuta recorrido("/tmp",{}) - se procesa hola.txt - se ejecuta recorrido("/tmp/uno",{'/tmp/hola.txt' : 123}) - se procesa uno.txt - se vuelve - se ejecuta recorrido("/tmp/dos",{'/tmp/hola.txt' : 123, '/tmp/uno/uno.txt' : 431}) - se procesa dos.txt - se vuelve - se vuelve

01 def recorre(ruta,hash): 02 entradas = os.listdir(ruta) 03 04 for entrada in entradas: 05 06 ruta_entrada = ruta + "/" + entrada 07 08 if os.path.isfile(ruta_entrada): 09 hash[ruta_entrada] = os.path.getsize(ruta_entrada) 10 elif os.path.isdir(ruta_entrada): 11 recorre(ruta_entrada, hash) 12 else: 13 print "ERROR"

Listado 1: funcin recorre

enfrentamos. En caso de que sea un fichero se usa como entrada en el hash su ruta, y el tamao del fichero como valor, que se consigue a travs de la funcin os.path.getsize(). Y qu pasa si es un directorio? Pues que se invoca a recorre() de nuevo! Pero esta vez se pasa la ruta de este subdirectorio. De esta manera, se vuelve a invocar recorre() para cada subdirectorio que se encuentre. Llegar un momento que no se encuentren ms subdirectorios, slo ficheros. Es entonces cuando se detiene la recursin. Imaginemos que se tiene /tmp/ y en su interior el fichero hola.txt y los subdirectorios uno y dos con los ficheros uno.txt y dos.txt respectivamente. Entonces la ejecucin sera como la mostrada en el Listado 2. El resultado final sera que el hash contendra los datos:
{'/tmp/hola.txt' : 123,U '/tmp/uno/uno.txt' : 431,U '/tmp/dos/dos.txt' : 3234 }

su ruta. Por eso se vuelve a generar la ruta usando la variable ruta_entrada para contenerla. Es ahora cuando viene lo difcil. No se sabe la clase de objeto que se tiene entre manos, as que se emplean las funciones os.path.isfile() y os.path.isdir() para ver con qu nos

La clusula else: se ha puesto para poder capturar errores, cosa que no debera pasar nunca. La recursividad, cuando se escapa de la mano, es muy complicada de controlar, pero su buen uso crea cdigo muy sencillo.

El trabajo duro
Es aqu cuando hay que emplear un poco de lgica. Existen muchas maneras de solucionar este problema, pero no tiene mucho sentido emplear las ms eficientes. Es preciso repartir los

ficheros entre contenedores de tamaos iguales especificados por el usuario, o sea, por nosotros mismos. Los mejores algoritmos conseguirn un aprovechamiento ptimo de los contenedores, pero vale la pena? Creo que la mayor parte de la gente dira que no. Por qu? Pues debido a que muchas veces gran parte de los ficheros estn relacionados entre s. No suele ser buena idea meter en un CDROM un fichero grande correspondiente a un captulo de alguna serie y cientos de ficheros pequeos no relacionados, mientras que el segundo captulo de la serie va en otro CDROM. El algoritmo ptimo es el que emplea Programacin Dinmica, una tcnica muy usada para resolver problemas de este tipo. Nosotros emplearemos uno ms sencillo y adecuado, un Algoritmo Voraz. Realmente no tiene mucha historia: comienza por los ficheros ms grandes, e intenta meter tantos como puedas en uno de los contenedores. Cuando no se pueda introducir el siguiente fichero que toque, se salta al siguiente contenedor. De esta manera, los ficheros que se encuentren en el mismo subdirectorio tendern a ir en grupos en los mismos contenedores. La funcin encargada de esta tarea es organiza(), que podemos ver en el Listado 3. Esta funcin hace uso de una caracterstica que no se suele encontrar en otros lenguajes, las funciones Lambda. Una funcin Lambda es una funcin annima, sin nombre. Se puede crear en cualquier momento, y lo que es ms importante, almacenarlas en variables. Como estn asociadas a una variable pueden ser pasadas como parmetros en las funciones. Y la funcin sort() admite 2 funciones como parmetro, de las cuales slo se hace uso de la primera. Por qu se necesita pasar una funcin Lambda? sort() ordena listas, pero la estructura de datos listado no es una lista simple. Es una lista que contiene listas de dos elementos. El primero es el nombre del fichero, y el segundo el tamao. Si se desean ordenar los ficheros por tamao ser necesario decirle a sort() que se fije en el segundo miembro de la lista, que es cada elemento de listado. Y eso es, justamente, lo que hace la funcin Lambda. El primer parmetro de sort() es una funcin que se usa para comparar dos

52

Nmero 14

WWW.LINUX- MAGAZINE.ES

Python DESARROLLO

Listado 3: cdigo del programa


001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 #!/usr/local/bin/python import os import os.path for fichero in dir[llave]: print " %12d \t\t\t %s" % (fichero[1], fichero[0]) 057 cont += fichero[1] 058 059 print "\nTotal: " + str(cont) + "/"+str(cantidad) 060 print "\n" 061 062 def print_caja(texto): 063 texto = " | " + texto + " |" 064 print " ."+ ((len(texto) - 3)*"-") + "." 065 print texto 066 print " `"+ ((len(texto) - 3)*"-") + "'" 067 068 def genera_dirs(ruta,datos): 069 070 for llave in datos.keys(): 071 072 cont = 0; 073 074 ruta_dir = ruta + "/dir-"+ str(llave) 075 076 if (os.path.exists(ruta_dir)): 077 print "Error, "+ruta_dir+" ya existe!!" 078 print "Elimine el directorio y vuelva a ejecutar el programa" 079 exit(-1) 080 081 os.mkdir(ruta_dir) 082 083 for fichero in datos[llave]: 084 ruta_ant = fichero[0] 085 ruta_nueva = ruta_dir + ruta_ant.split(ruta)[1] 086 os.renames(fichero[0],ruta_nueva) 087 088 def vuelca_indice(ruta, datos): 089 texto = "<?xml version=1.0>\n" 090 texto += "<coleccion>\n" 091 092 for llave in datos.keys(): 093 texto += "<contenedor id=\""+str(llave)+"\">\n" 094 ruta_dir = ruta + "/dir-"+ str(llave) 095 for fichero in datos[llave]: 096 texto +="<fichero>"+ ruta_dir + fichero[0].split(ruta)[1] + "</fichero>\n" 097 texto += "</contenedor>\n" 098 099 texto += "</coleccion>\n" 100 101 fich = open(ruta+"/indice.xml","w+") 102 fich.write(texto) 054 055 056

def recorre(ruta,hash): entradas = os.listdir(ruta) for entrada in entradas: ruta_entrada = ruta + "/" + entrada if os.path.isfile(ruta_entrada): hash[ruta_entrada] = os.path.getsize(ruta_entrada) elif os.path.isdir(ruta_entrada): recorre(ruta_entrada, hash) else: print "ERROR"

016 017 018 019 020 021 def organiza(hash, cantidad): 022 # genera una lista de hash con la cantidad ptima de ficheros para 023 # de manera que cada hash no puede contener ms de esa cantidad. 024 025 listado = hash.items() 026 027 compara = (lambda x,y: cmp(x[1],y[1])) 028 029 listado.sort(compara, reverse=True) 030 contador = 0 031 n = 0 032 directorios = {} 033 034 for entrada in listado: 035 if (contador + entrada[1] <= cantidad): 036 if not n in directorios.keys(): 037 directorios[n] = [] 038 directorios[n].append(entrada) 039 contador += entrada[1] 040 else: 041 n += 1 042 directorios[n] = [] 043 directorios[n].append(entrada) 044 contador = entrada[1] 045 046 return directorios 047 048 def print_directorio(dir, cantidad): 049 050 for llave in dir.keys(): 051 052 cont = 0; 053 print_caja("Directorio " + str(llave))

WWW.LINUX- MAGAZINE.ES

Nmero 14

53

DESARROLLO Python

Figura 1: Estado original de los directorios antes de correr el script. Tenemos un directorio inmenso de 1GB. Figura 2: El script nos parte el directorio original en directorios ms

Listado 3: cdigo del programa (continuacin)


103 fich.close() 104 105 if __name__ == '__main__': 106 107 hash = {} 108 ruta = "/usr/local/datos" 109 cantidad = 60000000 #en bytes 110 111 recorre(ruta,hash) 112 113 d = organiza(hash,cantidad) 114 print_directorio(d, cantidad) 115 116 genera_dirs(ruta,d) 117 118 vuelca_indice(ruta,d)

El siguiente paso pequeos y manejables. consiste en generar una estructura consistente en un dicSe puede ver un ejemplo en el Listado 4. I cionario o hash, donde la llave es un nmero y el contenido una lista de ficheros Listado 4: fichero de ndice y sus tamaos. Este hash representa cada uno de los contenedores que usaremos. 01 <?xml version=1.0> Cuando se llena uno pasamos a crear el 02 <coleccion> siguiente y as hasta quedarnos sin 03 <contenedor id="0"> ficheros.
04

Movimiento de ficheros
Ya slo nos queda recorrer la estructura de datos con genera_dirs(), que genera los directorios y mueve los ficheros a su nueva localizacin. Cabe resaltar la funcin os.path.split() que separa una ruta de fichero en dos, la primera parte de la ruta hasta el nombre del fichero y el nombre del fichero en s mismo. Aqu se usa para generar una nueva ruta. Para ello se emplea la funcin os.renames() que equivale a mover ficheros, pero tambin crea los directorios que estn en la ruta de destino y no existan.

elementos, y que devuelve -1, 0 y 1. Esa funcin ya existe y se llama cmp(). Se cre una funcin Lambda que se aplica a los segundos miembros de las dos listas que admite como parmetro. Veamos su uso:
>>> a = (lambda x,y:U cmp(x[1],y[1]) ... ) >>> a <function <lambda> at 0x81a5dcc> >>> a([0,1],[0,1]) 0 >>> a([0,2],[0,1]) 1 >>>

La guinda del pastel


Como somos grandes hackers y estamos a la ltima se ha incorporado al programa una ltima funcin. La funcin vuelca_indice() genera un fichero XML con el ndice de contenedores y ficheros. El objetivo es que quien quiera crear un programa que grabe estos contenidos podr facilitarse mucho la tarea leyendo este ndice en lugar de tener que escanear los directorios. Una de las grandes ventajas del XML es que resulta muy sencillo generarlo. La estructura de nuestro fichero es bien simple: Coleccin Contenedor Fichero

<fichero>/usr/local/datos/dir0/videos/rubyonrails.mov</fich ero> 05 </contenedor> 06 <contenedor id="1"> 07 <fichero>/usr/local/datos/dir1/ase2001-gfkf.ps</fichero> 08 <fichero>/usr/local/datos/dir1/icse2002-gk.ps</fichero> 09 <fichero>/usr/local/datos/dir1/esop2003-gfkf.ps</fichero> 10 <fichero>/usr/local/datos/dir1/continuations.ps</fichero> 11 <fichero>/usr/local/datos/dir1/esop2001-gkvf.ps</fichero> 12 </contenedor> 13 </coleccion>

es importante fijarse como a pasa a convertirse en un funcin como cualquier otra.

Jos Mara Ruiz actualmente est realizando el Proyecto Fin de Carrera de Ingeniera Tcnica en Informtica de Sistemas. Lleva 8 aos usando y desarrollando software libre y, desde hace dos, se est especializando en FreeBSD.

54

Nmero 14

WWW.LINUX- MAGAZINE.ES

EL AUTOR

Vous aimerez peut-être aussi