10 de diciembre de 2011

Bash Script: Comparando archivos

Bueno, bueno, bueno... Hace tiempo que tenía ganas de hacer una entrada con algo de programación de scripts en Bash pero no encontraba el momento ni el código apropiado para colgarlo. En el trabajo me pidieron que comparase unos archivos y completase uno de ellos con datos del segundo. Como no tenía ganas de hacerlo manualmente ni de crear una macro Excel, pensé en un pequeño script que lo hiciera por mi. Os pongo en situación...

Tenemos un archivo Excel con una serie de datos de unas sedes en los que tenemos instalados unos equipos. Pensemos que tenemos las siguientes columnas:
- Sede
- Hostname
- IP
- Marca
- Modelo
- S/N (Reservado para los datos que tenemos que añadir)
En el segundo archivo tenemos una serie de datos que hemos recopilado de los distintos equipos. Imaginemos:
- Hostname
- IP
- S/N
La idea es comparar ambos archivos a partir del hostname y añadir el S/N (Número de Serie) a los datos del archivo original. Lo primero que vamos a hacer es crear un archivo CSV a partir del Excel original, por lo que nos quedaría un archivo parecido a:
Sede;Hostname;IP;Marca;Modelo;S/N;;;;;;
Sede1;Sede1;10.10.10.10;Cisco;1841;;;;;;;
Sede2;Sede2;10.10.10.11;Cisco;2801;;;;;;;
Mientras que el segundo tendrá la siguiente estructura:
Sede1 10.10.10.10 1234567890
Sede2 10.10.10.11 2345678901
Leeremos línea a línea el archivo con los número de serie, llamémosle NumeroSerie, y filtraremos el archivo original, llamémosle Planta, a partir del hostname. Luego con otro bucle leeremos todos los campos de la fila seleccionada y los mandaremos separados por coma a un nuevo fichero para que nos genere un CSV final tal que así:
Sede;Hostname;IP;Marca;Modelo;S/N;;;;;;
Sede1;Sede1;10.10.10.10;Cisco;1841;1234567890;;;;;;
Sede2;Sede2;10.10.10.11;Cisco;2801;2345678901;;;;;;
El código del script es:
#!/bin/bash
#

### VARIABLES
FILEORIG="Planta"
SERIALSORIG="NumeroSerie"
FILETARGET="Nuevo.csv"
CSVCOLS="6"

### FUNCTIONS
function Errors() {
        [ "$#" != "2" ] && echo -e "Fallo de ejecucion" && exit 1

        if [ "$1"  == "name" ]; then
          PARAM="nemonico"
        elif [ "$1" == "serial" ]; then
          PARAM="numero de serie"
        fi

        echo -e "Falta $PARAM en la linea '$2'" >> Error.log
}

### SCRIPT
sort $SERIALSORIG | uniq > NewSerials

FILESERIALS="NewSerials"

TOTAL="`cat $FILESERIALS | wc -l`"

head -n 1 $FILEORIG >> $FILETARGET

for ((i=1;i<=$TOTAL;i++)); do

  ARCHIVO="`cat $FILESERIALS | grep -n . | grep -E "^$i:"`"
  NEMONICO="`echo $ARCHIVO | cut -d ' ' -f 1 | sed 's/[0-9]*://g'`"
  SERIAL="`echo $ARCHIVO | cut -d ' ' -f 3`"

  [ -z $NEMONICO ] && Errors name "$ARCHIVO" && continue
  [ -z $SERIAL ] && Errors serial "$ARCHIVO" && continue

  for ((j=1;j<=$CSVCOLS;j++));do

    CAMPO="`cat $FILEORIG | grep $NEMONICO | cut -d ';' -f $j`"

    [ "$j" == "$CSVCOLS" ] && CAMPO="$SERIAL;;;;;;;"

    echo -ne "$CAMPO;" >> $FILETARGET

  done

  echo -ne "\n" >> $FILETARGET

done

rm -rf NewSerials

exit 0

#EOF
##FVE
Lo primero que hacemos es ordenar y quitar los repetidos del archivo NumeroSerie, contamos el número de líneas y lo pasamos al bucle que nos leerá el archivo línea a línea. Filtraremos el archivo NumeroSerie para quedarnos con el hostname y con el S/N de cada línea, NEMONICO y SERIAL respectivamente.
ARCHIVO="`cat $FILESERIALS | grep -n . | grep -E "^$i:"`"
NEMONICO="`echo $ARCHIVO | cut -d ' ' -f 1 | sed 's/[0-9]*://g'`"
SERIAL="`echo $ARCHIVO | cut -d ' ' -f 3`"
En caso que no exista el hostname o el numero de serie, enviamos a la función Errors una palabra clave acompañada de la línea donde ha fallado con lo que generamos un archivo de log, Error.log. Además cortamos la ejecución del bucle para que pase a la siguiente iteración.
[ -z $NEMONICO ] && Errors name "$ARCHIVO" && continue
[ -z $SERIAL ] && Errors serial "$ARCHIVO" && continue
En el segundo bucle leeremos el archivo Planta y lo filtramos a partir del hostname para recorrer todos los campos de la línea y generar un archivo CSV nuevo. En este caso los S/N van en la sexta columna (Variable CSVCOLS), por lo que cuando se encuentre en dicho campo utilizaremos el valor almacenado en la variable SERIAL para mandarlo al archivo final:
[ "$j" == "$CSVCOLS" ] && CAMPO="$SERIAL;;;;;;;"
El script no es nada del otro mundo: hay un pequeño control de errores, no hay funciones que estructuren bien el código, no está documentado, etc; pero me parece que puede ser interesante publicarlo porque es bastante genérico y fácilmente adaptable a otros proyectos o necesidades que puedan surgir.

Un saludo, Brixton Cat.

1 comentario:

  1. Muchas gracias, muy poca gente comparte cosas utiles e interesantes..

    ResponderEliminar

Bienvenid= si quieres dejar un comentario