Friday, August 14, 2009

Python: parsear un fichero y cambiar el contenido de una línea

Este script crea parsea el fichero de configuración del apache2 en la ruta que le indiquemos y substituye el DocumentRoot del VirtualHost que le indiquemos por uno nuevo que también le pasamos por parámetro:

./scriptillo.py <ruta_al_fichero_de_configuración> <VirtualHost> <nueva_ruta>

El contenido del fichero es:


#!/usr/bin/env python
# -*- coding: utf-8 -*-

from cStringIO import StringIO
import re

# r'<VirtualHost\s(.*?)>' significa cadena de tipo raw
# que coincide con <VirtualHost más cualquier cadena de espacios
# tabuladores ... lo que sea más cualquier carácter que no sea fin de línea
# o ninguno, y acabe en >
vhost_start = re.compile(r'<VirtualHost\s(.*?)>')
vhost_end = re.compile(r'</VirtualHost')
# r'(DocumentRoot\s+)(\S+)' significa cadena de tipo raw
# (DocumentRoot\s+) significa que cumple que tiene DocumentRoot más una
# o más espacios o similar; y además lo anterior va seguido de una cadena
# diferente de espacios en blanco.
docroot_re = re.compile(r'(DocumentRoot\s+)(\S+)')

def replace_docroot(conf_string, vhost, new_docroot):
'''yield new lines of an httpd.conf file where docroot lines matching
the specified vhost are replaced with the new_docroot
'''
conf_file = StringIO(conf_string)
# variable que controla si estamos en el VirtualHost correcto
in_vhost = False
# Inicializamos curr_vhost como objeto
curr_vhost = None
for line in conf_file :
# comprobamos si la línea es del tipo <VirtualHost ...>
vhost_start_match = vhost_start.search(line)
# Si lo es, asignamos curr_vhost el nombre al que
# responde el VirtualHost:<puerto> que estamos leyendo
if vhost_start_match :
curr_vhost = vhost_start_match.groups()[0]
in_vhost = True
# Si estamos en un VirtualHost y es el que le hemos indicado
# empezamos a buscar la línea de DocumentRoot y hacemos la substitución
if in_vhost and (curr_vhost == vhost) :
doc_root_match = docroot_re.search(line)
if doc_root_match :
sub_line = docroot_re.sub(r'\1%s' % new_docroot, line)
line = sub_line
# Comprobamos si hemos llegado al final del VirtualHost
# y si es así reseteamos la variable in_vhost
vhost_end_match = vhost_end.search(line)
if vhost_end_match :
in_vhost = False
yield line

if __name__=='__main__' :
import sys
# fichero de configuración de apache2
conf_file = sys.argv[1]
# VirtualHost cuyo DocumentRoot hay que modificar
vhost = sys.argv[2]
# nuevo DocumentRoot
docroot = sys.argv[3]

# leemos del fichero de configuración
conf_string = open(conf_file).read()
# para cada línea llamamos a la función encargada de hacer la
# substitución si es necesario
for line in replace_docroot(conf_string,vhost,docroot):
print line

Thursday, August 13, 2009

Python: expresiones regulares (2)

Se trata de ampliar un poco lo de las expresiones regulares con un par de ejemplos:

import re
vhost_start = re.compile(r'<VirtualHost\s(.*?)>')
  • r al principio signifa que es una raw string por eso no escapo el <
  • <VirtualHost\s significa que buscamos <VirtualHost seguido de cualquier carácter de espacio en blanco (\t\n\r\f\v)
  • (.*?) el . significa cualquier carácter menos newline, * significa n veces la condición anterior, ? precedido de * significa al menos 1 vez la condición anterior
  • > que acabe en >

docroot_re = re.compile(r'(DocumentRoot\s+)(\S+)')

  • r'...' significa cadena de tipo raw
  • (DocumentRoot\s+) significa que cumple que tiene DocumentRoot más uno o más espacios o similar
  • (\S+) lo anterior va seguido de una cadena diferente de espacios en blanco.

Python: expresiones regulares

Para trabajar con expresiones regulares en python hay que importar el módulo re, que contiene:

import re

help(re)
....
This module exports the following functions:
match Match a regular expression pattern to the beginning of a string.
search Search a string for the presence of a pattern.
sub Substitute occurrences of a pattern found in a string.
subn Same as sub, but also return the number of substitutions made.
split Split a string by the occurrences of a pattern.
findall Find all occurrences of a pattern in a string.
finditer Return an iterator yielding a match object for each match.
compile Compile a pattern into a RegexObject.
purge Clear the regular expression cache.
escape Backslash all non-alphanumerics in a string.
....


Las búsquedas se pueden hacer compilando previamente el patrón o sin compilarlo.

Sin compilar:
#!/usr/bin/env python
import re

patron=";;(.*?);;"
texto="aquí el nombre ;;campo1;; y aquí los apellidos ;;campo2;;"

for campo in re.findall(patron,texto):
print "Coincidencia : ",campo


Compilando: (creando el objeto expresión regular)
#!/usr/bin/env python
import re

patron=re.compile(";;(.*?);;")
texto="aquí el nombre ;;campo1;; y aquí los apellidos ;;campo2;;"

for campo in patron.findall(texto):
print "Coincidencia : ",campo


La diferencia está en que con el segundo proceder creamos un objeto "expresión regular" que contendrá los métodos propios de una expresión regular. P.e. findall, search ... La segunda diferencia es que al llamar al método findall en el primer caso accedemos al módulo y en el segundo al método del objeto.

Sobre una única ejecución en un fichero medio no va mucha diferencia, aunque la hay. A continuación pongo un ejemplo en que sí la hay al abrir un fichero de log de 80000 líneas y hacer una búsqueda repetidamente.

Compilando: (creando el objeto expresión regular)



#!/usr/bin/env python
# -*- coding: utf-8 -*-

import re

def run_re():
pattern = "ass"
re_obj = re.compile(pattern)
infile = open ("/var/log/dpkg.log.1","r")

match_count = 0
lines = 0

for line in infile:
match = re_obj.search(line)
if match :
match_count += 1
lines += 1
return (lines , match_count)

if __name__=="__main__" :
lines,match_count = run_re()
print "Lines :: ",lines
print "Matches :: ",match_count


Sin crear el objeto expresión regular:


#!/usr/bin/env python
# -*- coding: utf-8 -*-

import re

def run_re():
pattern = "ass"
infile = open ("/var/log/dpkg.log.1","r")

match_count = 0
lines = 0

for line in infile:
match = re.search(pattern,line)
if match :
match_count += 1
lines += 1

return (lines,match_count)

if __name__=='__main__' :
lines,match_count = run_re()
print "Lines :: ",lines
print "Matches :: ",match_count