LinuxParty

NUESTRO SITIO necesita la publicidad para costear hosting y el dominio. Por favor considera deshabilitar tu AdBlock en nuestro sitio. También puedes hacernos una donación entrando en linuxparty.es, en la columna de la derecha.

Ratio: 5 / 5

Inicio activadoInicio activadoInicio activadoInicio activadoInicio activado
 

Para retomar el capítulo de Scripting Linux, usando el Shell Bash, vamos a resolver uno de los ejercicios prácticos de las pasadas oposiciones a Profesor Técnico de Formación Profesional (PTFP), especialidad Aplicaciones y Sistemas Informáticos (SAI)

Script Linux de las Oposiciones 2015 al cuerpo FP SAI

Script Linux de las Oposiciones 2015 al cuerpo FP SAI

El enunciado rezaba algo así:

Dado un fichero de datos con el siguiente contenido de ejemplo (digamos datos.txt):

Pepe 02:30:44
Marcos 23:56:33
Pepe 10:33:01
Marta 05:47:44
Pepe 12:22:33
José 11:55:00

Haced un Script Linux que devuelva un listado ordenado por tiempos (de uso de máquina por ejemplo) de forma ascendente. Teniendo en cuenta que si algún usuario se repite, solo produzca una línea de salida y sume los tiempos.

Al lío: Lo vamos a plantear mediante un bucle while … do … done al que le “entubamos” el fichero de datos ya ordenado de forma que los usuarios repetidos aparezcan de forma consecutiva al procesarlos.

Esto lo podemos conseguir así:

1 cat datos.txt|sort > datos2.txt

Con esto el fichero de datos inicial datos.txt lo transformamos en otro datos2.txt con este contenido:

José 11:55:00
Marcos 23:56:33
Marta 05:47:44
Pepe 02:30:44
Pepe 10:33:01
Pepe 12:22:33

Ahora como veis Pepe que aparecía desperdigado 3 veces, lo encontraremos de forma consecutiva.

Para leer el archivo de datos del que partimos datos2.txt ya ordenado por el primer campo (nombre) y al no decirle nada de forma ascendente (A-Z) usaremos un bucle de esta guisa:

  while read persona tiempo
do
 
  ...
 
done < datos2.txt

 Con este magnífico bucle, leemos, dado que el separador entre usuario y tiempos es el espacio en blanco, mediante el comando read persona tiempo, 2 variables una llamada persona, que al utilizarla referenciaremos como $persona (acordaos que para usar el contenido añadimos el símbolo del dolar), y otra tiempo, que referenciaremos como $tiempo.

Y digo magnífico bucle, porque fijaos como “chupa” el fichero de datos, al final del bucle, en el cierre del mismo, al done le “entubamos” el fichero datos2.txt, el ordenado, mediante el redirector de entrada estándar (el menor que <).

Si el fichero a leer, o los trozos/variables a extraer no utilizasen el separador “espacio en blanco” se usa esta sintaxis alternativa:

  while IFS=";" read persona tiempo
do
 
  ...
 
done < datos2.txt

Con el añadido IFS=”;” le decimos que los datos de cada línea del fichero están separados por un punto y coma.

Como dado el caso tendremos que sumar tiempos, en caso de usuario repetido, podemos trocear las horas, minutos y segundos mediante el comando cut, imaginemos que nos viene la línea:

José 11:55:00

Como estamos leyendo 2 variables en el while, persona y tiempo, podemos trocear la segunda así:

  #aislo trozos de tiempo
horas=`echo $tiempo|cut -f1 -d:`
minutos=`echo $tiempo|cut -f2 -d:`
segundos=`echo $tiempo|cut -f3 -d:`

Con el comando cut -f1 -d: estoy pidiendo del flujo de datos el primer trozo o campo (field) que especifico con la opción -f1 (f de field, 1 de primera posición) y también informo que el separador de campos es el carácter dos puntos, mediante -d: (delimiter :).

En el caso de que el carácter de separación fuese el espacio en blanco, se especificaría así -d” “. Es decir, habría que entrecomillar un espacio en blanco.

El siguiente hito de información es controlar si durante la evolución del bucle, la persona a procesar es la misma que la anterior. Por lo que, tanto para los trozos de tiempo, como para el nombre de usuario utilizaremos unas variables secundarias que inicializaremos antes de nada:

  personaanterior=" "
horasanterior=0
minutosanterior=0
segundosanterior=0

Además pensemos un momento, según los datos de ejemplo, me vendrá primero José, tenemos que averiguar si se trata de la misma persona que en la iteración anterior del bucle. Para preguntar esto último utilizaremos la estructura condicional if, más o menos así:

 

  if [ "$persona" = "$personaanterior" ]; then
 
  ...
 
else
 
  ...
 
fi

Lo de encerrar las variables, que se supone tienen cadenas de texto dentro, entre comillas dobles, es uno de mis “por si …“, de forma que no de error de sintaxis en caso de que alguna de las dos esté vacía. Fijaos también en las separaciones que hay entre los corchetes, los operandos y el operador de comparación, de lo contrario ERROR.

Posibles errores al utilizar if..then..else..fi si no separamos los corchetes, operandos y operadores

Posibles errores al utilizar if..then..else..fi si no separamos los corchetes, operandos y operadores

Siguiendo con lo nuestro, en el caso de que la persona sea la misma que la anterior, lo que debemos hacer es sumar los tiempos. Cosa que haremos en el primer bloque, el bloque VERDADERO, en los primeros “…” de la estructura condicional usada. Pero si no se cumple la condición, OJO puede que se trate de la primera iteración, y que “José” sea distinto de ” “, que es como hemos inicializado la variable personaanterior, justo antes de entrar al bucle. En ese caso, no escribimos aun en el fichero resultado.txt, sino que tenemos que contemplar este caso especial de “1ª ITERACIÓN“, para ello podríamos dentro del bucle, ir incrementando un contador, que inicialmente fuera del mismo hayamos inicializado a cero.

 
...
contador=0
 
while read persona tiempo
do
  contador=`expr $contador + 1`
 
  ...

 Mediante el comando expr podemos hacer operaciones aritméticas sencillas, en este trozo anterior al contenido de la variable contador ($contador) le sumamos 1. Utilizamos el operador grave `…` para que se ejecute la orden y el resultado se lo asigne a la variable de la izquierda del igual, el mismo contador PERO sin el símbolo dolar. Fijaos además de la separación que hay entre los operandos y la operación suma, de lo contrario dará ERROR. Por ejemplo esto fallaría

expr $contador +1

Posible error en el comando expr si no separamos operandos y operador por un espacio en blanco

Posible error en el comando expr si no separamos operandos y operador por un espacio en blanco

Volviendo a nuestro if … then … else … fi:

 

 
  if [ "$persona" = "$personaanterior" ]; then
    echo "La misma $persona sumo y espero"
    #sumamos tiempos
  ...
  else
    echo "distinta $persona"
    #primero escribo la anterior a no ser que sea 1era iteración
    if [ $contador -ne 1 ]; then
      echo "$personaanterior$horasanterior:$minutosanterior:$segundosanterior" >> resultado.txt
    fi
    #reasigno
    personaanterior=$persona
    horasanterior=$horas
    minutosanterior=$minutos
    segundosanterior=$segundos
fi

Como sumamos los tiempos? Teniendo en cuenta que si sumamos los segundos actuales con los anteriores y llegan o pasan de 60 habría que añadir a los minutos, y mismo cuento para los minutos-horas, con algo así:

 
  if [ "$persona" = "$personaanterior" ]; then
    echo "La misma $persona sumo y espero"
    #sumamos tiempos
    horasanterior=`expr $horasanterior + $horas`
    minutosanterior=`expr $minutosanterior + $minutos`
    if [ $minutosanterior -ge 60 ]; then
      horasanterior=`expr $horasanterior + 1`
      minutosanterior=`expr $minutosanterior - 60`
    fi
    segundosanterior=`expr $segundosanterior + $segundos`
    if [ $segundosanterior -ge 60 ]; then
      minutosanterior=`expr $minutosanterior + 1`
      segundosanterior=`expr $segundosanterior - 60`
    fi
  else
    echo "distinta $persona"
    #primero escribo la anterior a no ser que sea 1era iteración
    if [ $contador -ne 1 ]; then
      echo "$personaanterior$horasanterior:$minutosanterior:$segundosanterior" >> resultado.txt
    fi
    #reasigno
    personaanterior=$persona
    horasanterior=$horas
    minutosanterior=$minutos
    segundosanterior=$segundos
fi

 Y ya casi lo tenemos, falta ensamblar las partes y tener en cuenta que la última persona según nuestro algoritmo no se imprimiría.

 Una solución rápida sería imprimirla fuera del bucle, ya que si era la misma que la anterior sumábamos tiempos y esperábamos (caso de Pepe que aparece 3 veces), y si era distinta imprimíamos la anterior y reasignábamos a la espera de la siguiente iteración, así pues se cumplen todos los hitos, y la última persona, sea o no sea repetida en el fichero original, la imprimiremos justo después de terminar el bucle.

 Y un detalle más, el enunciado decía “Ordenados de forma ascendente por tiempo“, eso lo solucionamos rápidamente mediante la opción -kN del comando sort, así:

sort -k2 resultado.txt

El comando anterior ordena por el segundo campo o clave (key), el primero seria el nombre, y el segundo el tiempo.

¿Que pasa en caso de empate de horas? Solucionalo tu y envianos la solución.

Aquí va el código completo:

 
#!/bin/bash
rm resultado.txt
cat datos.txt|sort > datos2.txt
 
personaanterior=" "
horasanterior=0
minutosanterior=0
segundosanterior=0
contador=0
 
while read persona tiempo
do
  contador=`expr $contador + 1`
 
  #aislo trozos de tiempo
  horas=`echo $tiempo|cut -f1 -d:`
  minutos=`echo $tiempo|cut -f2 -d:`
  segundos=`echo $tiempo|cut -f3 -d:`
 
  echo "Actual: $persona - $horas - $minutos - $segundos"
 
  if [ "$persona" = "$personaanterior" ]; then
    echo "La misma $persona sumo y espero"
    #sumamos tiempos
    horasanterior=`expr $horasanterior + $horas`
    minutosanterior=`expr $minutosanterior + $minutos`
    if [ $minutosanterior -ge 60 ]; then
      horasanterior=`expr $horasanterior + 1`
      minutosanterior=`expr $minutosanterior - 60`
    fi
    segundosanterior=`expr $segundosanterior + $segundos`
    if [ $segundosanterior -ge 60 ]; then
      minutosanterior=`expr $minutosanterior + 1`
      segundosanterior=`expr $segundosanterior - 60`
    fi
  else
    echo "distinta $persona"
    #primero escribo la anterior a no ser que sea 1era iteración
    if [ $contador -ne 1 ]; then
      echo "$personaanterior$horasanterior:$minutosanterior:$segundosanterior" >> resultado.txt
    fi
    #reasigno
    personaanterior=$persona
    horasanterior=$horas
    minutosanterior=$minutos
    segundosanterior=$segundos
fi
 
done < datos2.txt
 
#escribo la ultima
echo "$personaanterior$horasanterior:$minutosanterior:$segundosanterior" >> resultado.txt
 
sort -k2 resultado.txt

 

Pin It

Escribir un comentario


Código de seguridad
Refescar



Redes:



 

Suscribete / Newsletter

Suscribete a nuestras Newsletter y periódicamente recibirás un resumen de las noticias publicadas.

Donar a LinuxParty

Probablemente te niegues, pero.. ¿Podrías ayudarnos con una donación?


Tutorial de Linux

Filtro por Categorías