Muitas vezes você pode ter uma grande quantidade de arquivos contendo dados e deseja organizá-las, de modo que cada linha corresponda a informação obtida em um determinado horário. Geralmente os algoritmos desenvolvidos buscam um melhor desempenho da máquina apoiando-se em padrões como nomes de arquivos, ordenamento de datas utilizando dia do ano e número de segundos desde uma determinada data, etc.
Caso os dados estejam realmente uma bagunça, aqui segue um algoritmo inverso: data e horário são criados em uma string que é buscada em toda a base de dados, copiando as linhas encontradas para um novo arquivo; um comando em awk apaga as linhas duplicadas sem alterar a ordem (ou seja, sem utilizar o comando “sort”). É bem mais demorado computacionalmente (por isso inclui um comando juntando toda a informação em um único arquivo, para não precisar ficar abrindo e fechando arquivos para leitura), mas é uma opção caso não possa contar com uma melhor organização na base de dados.
#!/bin/bash # Programa para ler linhas de arquivo contendo datas e organizar em ordem cronológica, sem repetições # Formato da linha: 1,2013,169,937,0,2.332,.878 onde id,ano,dia_do_ano,HHMM,dado1,dado2,dado3 # obs: 1 = 00:01, 100 = 01:00 function coloca_zero(){ a=$1 b=`echo "$a" | wc -L | awk '{print $1}'` while [ $b != 2 ]; do b=$((b+1)) a=0$a done echo $a } cat *PI* > dados.dat # Opção para concatenar arquivos sem quebra de linha como último caractere #for file in $(ls *PI*); do # cat $file # echo #done > dados.dat # Outra opção #paste --delimiter=\\n --serial *PI* linha=1 for ano in $(seq 2012 2014); do for dia_do_ano in $(seq 1 366); do for hora in $(seq 0 23); do for min in $(seq 0 59); do if [ "$hora" -eq "0" ]; then horas='' minuto=$min else horas=$hora minuto=`coloca_zero "$min"` fi exp=`echo 1,$ano,$dia_do_ano,$horas$minuto,` grep $exp dados.dat done done done done > arq.dat #Eliminar linhas duplicadas SEM alterar ordem das linhas #sort arq.dat | uniq > arq_final.dat awk '!($0 in a) {a[$0];print}' arq.dat > arq_PI.dat rm dados.dat arq.dat
Esse outro script tem a função de calcular médias a cada cinco minutos com eventuais falhas na sequência de dados. É composto de duas funções: “coloca_zero”, que deixa dia, hora e minuto com dois dígitos, e “calcula_media”, que calcula a média coluna por coluna (usando “bc“) e monta a linha a ser impressa no arquivo. Essa média envolve os valores obtidos nos cinco minutos anteriores; por exemplo, dos minutos 55, 56, 57, 58 e 59 a média será representada no minuto 00 da hora (ou dia, se for o caso) seguinte.
Atualizações: Incluída a opção de corrigir os dados conforme o fuso horário (por exemplo, em São Paulo, para passar de hora UTC para HLE, usar fuso=-3). E também segue uma alternativa à função coloca_zero(): mes=`expr $m + 0`; mes_zero=`printf “%02d” $mes` . A respeito do comando cut, usar “-f2” imprime só a segunda coluna, enquanto que “-f2-” (com um traço depois) imprime todas as colunas da segunda para frente.
#!/bin/bash # Script para calcular médias a cada cinco minutos # com eventuais falhas na sequência de dados # Formato dos dados: # 2013 10 20 08 33 16.47 93.96 927.8137 0.00 0.00 4.57 infile='dados.dat' fuso=-3 function coloca_zero(){ a=$1 b=`echo "$a" | wc -L | awk '{print $1}'` while [ $b != 2 ]; do b=$((b+1)) a=0$a done echo $a } function calcula_media(){ arq=$1 minprint2=$2 hh=$3 dd=$4 colunas=14 nlinhas=$(cat $arq | wc -l) for (( cont=1; cont<=$colunas; cont++ )); do # Escolher precisão conforme a variável if [ "$cont" -ge 1 ] && [ "$cont" -le 5 ]; then SC=0 # data/horário elif [ "$cont" -eq 8 ]; then SC=4 # pressão else SC=2 fi S=`echo "("$(cut -d' ' -f"$cont" $arq | tr "\n" "+")"0)/$nlinhas"` R=`bc -l << fim scale=$SC $S fim` # não deixar caracter logo antes do fim if [ "$cont" -eq 5 ]; then R=$(($R+3)) fi echo $R >> temp3 done # Passar tudo pra uma linha só, com campos separados por espaço var=`cat temp3 | sed ':a;N;s/\n/ /g;ta'` rm temp3 # alterar minuto para indicar que é média dos últimos 5 minutos var1=`echo $var | cut -d' ' -f1` var2=`echo $var | cut -d' ' -f2` var2=`coloca_zero "$var2"` #var3=$dd #var4=$hh #var5=$minprint2 var6=`echo $var | cut -d' ' -f6` var7=`echo $var | cut -d' ' -f7` var8=`echo $var | cut -d' ' -f8` var9=`echo $var | cut -d' ' -f9` var10=`echo $var | cut -d' ' -f10` var11=`echo $var | cut -d' ' -f11` var12=`echo $var | cut -d' ' -f12` var13=`echo $var | cut -d' ' -f13` var14=`echo $var | cut -d' ' -f14` var15=`echo $var | cut -d' ' -f15` # Passar de UTC para HLE (tirar 3h) horatemp=$(($hh+$fuso)) if [ "$horatemp" -lt 0 ]; then diatemp=$(($dia-1)) # arrumar hora case $horatemp in "-1") horatemp=23; ;; "-2") horatemp=22; ;; "-3") horatemp=21; ;; esac else diatemp=$dia fi # Correção minuto/hora/dia if [ "$minprint2" -eq 60 ]; then minprint2=00 horatemp2=$(($horatemp+1)) hh2=`coloca_zero "$horatemp2"` if [ "$horatemp2" -eq 24 ]; then hh2=00 dia=$(($diatemp+1)) diatemp=`coloca_zero "$dia"` fi horatemp2=$hh2 else horatemp2=$horatemp fi var3=`coloca_zero "$diatemp"` var4=`coloca_zero "$horatemp2"` var5=`coloca_zero "$minprint2"` echo $var1-$var2-$var3 $var4:$var5,$var6,$var7,$var8,$var9,$var10,$var11,$var12,$var13,$var14 } ano=2014 mm=01 for dia in $(seq 8 23); do dd=`coloca_zero "$dia"` for hora in $(seq 0 23); do hh=`coloca_zero "$hora"` for minuto in $(seq 0 5 59); do #min=`coloca_zero "$minuto"` minprint=$(($minuto+5)) minprint2=`coloca_zero "$minprint"` # Montar strings a serem pesquisadas dentro de 5 min for i in $(seq 0 4); do min=$(($minuto+$i)) min2=`coloca_zero "$min"` # Correção minuto/hora/dia para busca if [ "$min" -eq 60 ]; then min2=00 horatemp2=$(($hora+1)) hh=`coloca_zero "$horatemp2"` if [ "$hora" -eq 24 ]; then hh=00 horatemp2=0 dia=$(($dia+1)) dd=`coloca_zero "$dia"` fi else horatemp2=$hora fi #echo $horatemp2 exp="$ano $mm $dd $hh $min2" echo $exp grep "$exp" $infile >> temp done echo fim_da_media size_file=`wc -c temp | cut -d' ' -f1` if [ $size_file -ne 0 ]; then # Substituir sequências de caracteres em branco repetidos por um único tr -s ' ' < temp > temp2 # Calcular media e gravar em novo arquivo; apagar temporarios media=`calcula_media temp2 $minprint2 $horatemp2 $dd` #new_file=estacao_$ano-$mm-$dd.dat new_file=estacao_tdjunto_$ano-$mm.dat echo $media >> files/$new_file rm temp2 fi rm temp done done done 2> organiza.log
O algoritmo grava todos os dados em um mesmo arquivo. Para separar em arquivos diários, segue a rotina “separa_dias.sh”:
#!/bin/bash # Programa para ler linhas de arquivo contendo datas e separar em arquivos diários # Formato da linha: 2013-10-04 22:45,20.19,89.00,928.5127,0,0,3.85,8.10,145.81,145.77 function coloca_zero(){ a=$1 b=`echo "$a" | wc -L | awk '{print $1}'` while [ $b != 2 ]; do b=$((b+1)) a=0$a done echo $a } for ano in $(seq 2013 2014); do for mes in $(seq 1 12); do for dia in $(seq 1 31); do mm=`coloca_zero "$mes"` dd=`coloca_zero "$dia"` exp=`echo $ano-$mm-$dd` grep $exp estacao_tdjunto.dat > estacao_$ano-$mm-$dd.dat done done done
Por último, caso precise substituir os valores de NaN (not a number) por -999 (por exemplo), utilize:
for i in `ls *.dat` do sed "s/NA/-999/g" $i > $i.NEW mv $i.NEW $i done
Segue também um outro script para fazer o inverso: criar um arquivo para cada data (se essa data existir) a partir de um arquivo com várias linhas de diferentes horários e dias.
#!/bin/bash # Script para separar dados em arquivos diários file_in=GERAL_2015.DAT ano=2015 # Para cada número entre 1 e 366, realizar comandos for dia in $(seq 1 366); do filename='PP_'$ano'-'$dia'.dat' # Contar número de linhas que contenham o texto entre aspas var=`grep "1,$ano,$dia," $file_in | wc -l` # Gravar arquivo somente se tiver linhas a serem copiadas if [ $var -ne 0 ]; then echo $filename grep "1,$ano,$dia," $file_in > $filename fi done
Algo que facilita a vida nesse arquivo é que os dias estão em “dia do ano”.
Extra sobre o comando sort
Esse comando serve para montar uma lista a partir dos registros da 1ª coluna de um arquivo, sem repetições:
cat dados/est1.csv | awk -F',' '{print $1}' | sort | uniq -d
Pode ser útil para identificar quais são os sensores que estão em um mesmo arquivo, por exemplo. Para organizar todas as linhas e colunas com uma coluna como referência, sue o parâmetro “-k” seguido do número da coluna de referência (começando de 1, da esquerda para a direita).