sebb.info
"Write programs that handle text streams, because that is the universal interface."
Creadores del sistema Unix.



> Blox, un parser orientado a bloques de texto - Actualizado, versión 0.44

  Porqué escribí blox:
  Internet es para mí la mejor fuente de datos, tanto para uso profesional como personal. Llevo casi 10 años escribiendo scripts para recuperar información online, desde horarios de cines hasta bases de datos complejas, informacíon meteorológica, números de teléfono, fax, emails, etc. La información está en internet, pero es muy tedioso recuperar a mano listados grandes y buscar en un mar de datos. Por eso mismo uso scripts que automatizan la tarea y me presentan los datos finales para que los pueda elegir y usar con facilidad una vez metidoss en una base de datos sql o en M$ Access. Los scripts se basan en bucles que recorren páginas webs y que eliminan toda la información que no interesa en la página, publicidad, maquetación, etc. Luego queda formatear la salida de texto, reconocer los campos e introducirlo todo en una base de datos para poder finalmente usar la información.

  Hay ciertos listados que llamo "secos", es decir que no tienen referencias textuales o html que permitan separar con facilidad el conjuto de lineas que pertenece a un registro de otro. Entonces empiezan larguísimas peleas con la línea de comandos, invocaciones a egrep e incantaciones a dios sed, loado sea Su Nombre. Pero con eso y con todo, la cosa sigue penosa, y a veces casi imposible. Todo esto me llevó a escribir el script blox, que da facilidad y flexibilidad a la hora de maquetar listas interminables. Creé blox pensando en comandos usuales, como:
  grep -A1 -B1 "algo"
  sed -n /principio/,/fin/p
  sed -n '45,50p'

  Ver el imprescindible "Sed one liners"

  Ejemplos de uso de blox:
./blox -I "Granada" -F "Ferretería" -a texto.txt -c Fax -n 1-10
Busca en un listado (el archivo "texto.txt") los grupos de líneas (bloques) cuyas primeras líneas contienen la palabra "Granada" y cuyas últimas contienen la palabra "Ferretería", presentando los diez primeros bloques que contienen la palabra "Fax". Si tenemos un listado de miles de empresas, encontraremos así las 10 primeras ferreterías granadinas que tengan un número de fax.

./blox -I "Lugar" -F "hora" -a guiadecine
Da una lista de horarios de cine.

./blox -I "<p.*>" -F "</p>" -a pagina.html
Busca párrafos de html en múltiples líneas. Como se ve, blox funciona también con expresiones regulares.

./blox -I "<p.*>" -F "</p>" -a pagina.html | sed -e "s/class=rojo/class=verde/g"
Cambia el código fuente html. Eso permite hacer un cambio selectivo en un archivo.

  Más ejemplos con un archivo:
  Descarga este archivo de pruebas, es una recopilación de datos de varias estaciones meteorologícas del mundo (script con metar aquí), y corre los siguientes ejemplos:

./blox -I Station -F Phenomena -a muestra_tiempo.txt
Uso sencillo: un párrafo tras otro, numerados automáticamente.

./blox -I Station -F Phenomena -a muestra_tiempo.txt -n 1-2
Solo los dos primeros

/blox -I Station -F Phenomena -a muestra_tiempo.txt -c UTC -n1-2
Solo los dos primeros que contengan la cadena "UTC"

./blox -I Station -F Phenomena -a muestra_tiempo.txt -s "Otro registro"
Cambiando el separador por las palabras "Otro registro"

  Atención:
  Blox sigue en desarrollo. Al usar un máximo de builts-in de bash, matrices y tests con expresiones regulares, blox es rápido, pero si le metes más de 100.000 líneas de texto en un P-III con poca ram, puede tardar un poco en darte el resultado que esperas. (Todo depende de cuanto sea "un poco" para tí). Blox funciona a partir de bash versión 3.

  Atención 2:
  La ley y ciertas (numerosas) páginas prohiben la recuperación automática de los contenidos online. NO soy responsable si este artículo te induce a recopilar ilegalmente datos: es tu problema, NO lo hagas.

  Futuro:
  He empezado a desarrollarlo sobre Python y C ANSI, pero eso es, solo he empezado.

  Sinopsis:
blox -I "cadena inicial" -F "cadena final" -a "archivo" [-c "cadena"] [-n "número de bloque" [[-|,]"número de bloque"]] [-s] [-f]

Donde:
  -I "cadena inicial" es la cadena que inicia el bloque objeto de la búsqueda,
  -F "cadena final" es la cadena que termina el bloque objeto de la búsqueda,
  -a "archivo" es un archivo de texto con permisos de lectura que contiene líneas separadas por saltos de línea,
  -c "cadena" es un valor opcional buscado entre "cadena inicial" y "cadena final",
  -n ["número de bloque" [[-|,]"número de bloque"]] es un número opcional (rango o lista de números) de bloque(s) que desea extraer a stoud,
  -r solo presenta el recuento de bloques coincidentes, no compatible con [cnsf],
  -s cambia la marca de separación entre bloques,
  -f modo "forzado", obliga a seguir aunque haya un número desigual de marcas.
  -e modo "ejemplos", presenta ejemplos básicos de uso.

#!/bin/bash

# ^blox^ - parser orientado a bloques de texto

# recibe cadena de inicio de bloque
# recibe cadena de fin de bloque
# recorre un texto en bucle para meter cada línea en una matriz
# Busca por la cadena de inicio y final en cada iteración y lo marca en la matriz
# Verifica que la matriz tenga un número par de elementos, sino da parte.
# Reporta el texto en cada bloque que empieza por "cadena inicial" y  termina por "cadena final".
# Puede seleccionar bloques solos, por listas o rangos. Eventualmente presenta
# la línea con "cadena" o solo recuenta bloques.

# copyright (c) sebb@sebb.info - 2007 - GPL


clear
[[ "$BASH_VERSION" =~ "^3" ]] || ( echo "Necesitas bash versión 3 o superior, saliendo." && exit 1 )
num_args=$#
prog="blox"
version="0.44"
uso="echo -e '$prog V $version. Parser orientado a bloques de texto.\n\n\
$prog -I \"cadena inicial\" -F \"cadena final\" -a \"archivo\"\n     \
[-c \"cadena\"] [-n  \"número de bloque\" [[-|,]\"número de bloque\"]] \
[-s] [-f]\n\nDonde:\n  -I  \"cadena inicial\" es la cadena que inicia el \
bloque objeto de la búsqueda,\n  -F  \"cadena final\" es la cadena que \
termina el bloque objeto de la búsqueda,\n  -a  \"archivo\" es un archivo \
con permiso de lectura que texto\n      separado por saltos de línea, \n  \
-c  \"cadena\" es un valor opcional buscado entre \"cadena inicial\" y\n      \
"cadena final\",\n  -n  [\"número de bloque\" [[-|,]\"número de bloque\"]] es \
un número opcional\n      (rango o lista de números) de bloque(s) que desea \
extraer a stoud,\n  -r  solo presenta el recuento de bloques coincidentes, \
no compatible con\n      -[cnsf],\n  -s  modo \"silencioso\", quita la marca \
de separación entre bloques,\n  -f  modo \"forzado\", obliga a seguir aunque \
haya un número desigual de marcas.\n\n  $prog V $version - General Public \
Licence - Copyright sebb@sebb.info - 2007\n'"
ejemplos="echo -e 'Ejemplos:\n./$prog -I \"Granada\" -F \"Ferretería\" -a \
texto.txt -c Fax\nBusca en un listado los grupos de líneas (bloques) cuyas \
primeras\nlíneas contienen la palabra\"Granada\" y últimas la palabra \
\"Ferretería\".\n\n./$prog -I \"Granada\" -F \"Ferretería\" -a texto.txt \
-c Fax -n 1-10\nLo mismo, presentando los diez primeros bloques que contienen \
la palabra \"Fax\"\n\n./$prog -I \"

\" -F \"

\" -a pagina.html\nBusca \ párrafos de html en múltiples líneas\n\n./$prog -I \"Lugar\" -F \"hora\" -a \ guiadecine\nDa una lista de horarios de cine.\n\n./$prog -I \"

\" \ -F \"

\" -a pagina.html | sed -e \"s/class=rojo/class=verde/g\"\nCambia \ fuente html.\n'" filtra=0 forzado=0 recuento=0 bloq_num=0 separadorok="1" while getopts :I:F:a:c:n:rs:fe OPTION do case $OPTION in I) ini="${OPTARG}" ; [ "$ini" = "-F" -o "$ini" = " " -o -z "$ini" ] && eval "$uso" && exit 1 ;; F) fin="${OPTARG}" ; [ "$fin" = " " -o -z "$fin" ] && eval "$uso" && exit 1 ;; a) arc="${OPTARG}" ; [ -r $arc -a -s $arc ] || ( eval "$uso" && exit 1 ) ;; c) cad="${OPTARG}" ; [ "$cad" = "" ] || filtra=1 ;; n) n="${OPTARG}" ; bloq="$n" ; bloque_unico=1 ; [[ "$bloq" =~ "-" ]] && bloq_1=${bloq%-*} && bloq_2=${bloq#*-} && bloque_unico=2 ; [[ "$bloq" =~ "," ]] && bloque_unico=3 ;; r) recuento=1 ;; s) separador="${OPTARG}" ; [ "$separador" = "" -o "$separador" = " " ] && separadorok="1" || separadorok="2" ;; f) forzado=1 ;; e) eval $ejemplos ; exit 0 ;; ?) echo -e "Error en argumento $OPTIND.\n" ; eval "$uso" && exit 1 ;; esac done [ "$num_args" -lt "6" ] && echo "Número de argumentos inadecuado" && eval "$uso" && exit 1 [[ "$n" = "" ]] && bloque_unico=0 IFS=" " declare -a text declare -a lims declare -a cadenas # Cuenta desde la línea 1 contador=1 for linea in $(<$arc) do # Mete la linea en la matriz text[${#text[@]}]="$linea" # Mete un puntero si encuentra $cadena [ "$filtra" = "1" ] && [[ "$linea" =~ "$cad" ]] && cadenas[${#cadenas[@]}]="C;$contador" # Si encuentra la cadena inicio o fin, crea un puntero, lo marca y lo numera if [[ "$linea" =~ "$ini" ]] ; then lims[${#lims[@]}]="I;$contador" elif [[ "$linea" =~ "$fin" ]] ; then lims[${#lims[@]}]="F;$contador" fi contador=$((contador+1)) done [ "$filtra" -eq 1 -a ${#cadenas[@]} -eq 0 ] && echo -e "No se ha encontrado ninguna marca de inicio o final.\n" && exit 2 # Verifica que la matriz tenga un número par de elementos, sino # busca si es un inicio o un fin que está solo y da parte. if [ "${#lims[@]}" -eq "0" ] then echo -e "No se ha encontrado ninguna marca de inicio o final.\n" && exit 2 else # Si la matriz no está vacía num_I=0 num_F=0 for puntero in ${lims[@]} do [[ "$puntero" =~ "^I" ]] && num_I=$((num_I+1)) [[ "$puntero" =~ "^F" ]] && num_F=$((num_F+1)) done if [ "$num_I" != "$num_F" ] then # Dice donde están los errores echo -e "$prog\nHay $num_I marca(s) de inicio (\"$ini\") y $num_F marca(s) de fin (\"$fin\")." [ "$num_I" -gt "$num_F" ] && echo -e "\nSobra(n) $((num_I-num_F)) marca(s) de inicio.\n" \ || echo -e "\nSobra(n) $((num_F-num_I)) marca(s) de fin.\n" # Sigue solo si está forzado [ "$forzado" -eq 1 ] || exit 1 fi # Presentación de los datos function presenta() { if [ "$filtra" = "1" ] ; then for c in ${cadenas[@]} do linea_cad=${c#*;} if [ "$linea_cad" -ge "$inicio" -a "$linea_cad" -le "$final" -a "$inicio" -ne "$final" ] ; then bloq_num=$((bloq_num+1)) while [ "$inicio" -lt "$final" ] do echo "${text[$inicio]}" inicio=$((inicio+1)) done ( [ "$separadorok" = "1" ] && echo -e "\n-----------^$prog^$bloq_num^----------- \n" ) || \ ( [ "$separadorok" = "2" ] && echo -e "\n$separador \n" ) fi done else bloq_num=$((bloq_num+1)) while [ "$inicio" -lt "$final" ] do echo "${text[$inicio]}" inicio=$((inicio+1)) done ( [ "$separadorok" = "1" ] && echo -e "\n-----------^$prog^$bloq_num^----------- \n" ) || \ ( [ "$separadorok" = "2" ] && echo -e "\n$separador \n" ) fi } # Reporta las líneas de texto en cada bloque que empieza por "cadena inicial" y # termina por "cadena final", basándose en los números de línea de los punteros. # Eventualmente presenta la línea con "cadena". Permite elegir qué bloques presentar. # Solo contar if [ "$recuento" = "1" ] ; then if [ "$forzado" = "1" ] ; then echo "Con la opción -f \"Forzado\", no hay información de bloques disponible." && exit 1 else blks="$((${#lims[@]}/2))" [ "$blks" -ne "0" ] && echo "$blks bloques." && exit 0 || echo "No hay bloques coincidentes." && exit 1 fi fi # Todos if [ "$bloque_unico" = "0" ] then limite=0 # Mientras haya bloques, los saca while [ "${#lims[@]}" -gt "$limite" ] do # Saca un bloque limI=${lims[$limite]} limI=${limI:2} limite=$((limite+1)) limF=${lims[$limite]} final=${limF:2} limite=$((limite+1)) inicio=$((limI-1)) presenta done fi # Un bloque solo o una lista de bloques if [ "$bloque_unico" = "1" -o "$bloque_unico" = "3" ] then # Saca el bloque limI=${lims[$(((bloq*2)-2))]} limI=${limI:2} limF=${lims[$(((bloq*2)-1))]} final=${limF:2} inicio=$((limI-1)) presenta fi # Rango if [ "$bloque_unico" = "2" ] then cuantos=$((bloq_2-bloq_1+1)) max=0 while [ "$cuantos" -gt "$max" ] do limI=${lims[$(((bloq_1*2)-2))]} limI=${limI:2} limF=${lims[$(((bloq_1*2)-1))]} bloq_1=$((bloq_1+1)) final=${limF:2} inicio=$((limI-1)) presenta max=$((max+1)) done fi unset text unset lims unset cadenas exit 0 fi # FIN # # # # # # # # # # # # # # # # # #


  Si tienes alguna pregunta o mejora, escríbeme.
Primera publicación: 2 de Agosto del 2008. Última actualización: 23 de Agosto del 2008
El HTML40 cumple con las normas W3   La hoja de estilo CSS cumple con las normas W3