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).



