Eu tenho um arquivo que cresce cerca de 200.000 linhas por dia e é formado com blocos de três linhas, como tal:
1358726575123 # key
Joseph Muller # name
carpenter # job
9973834728345
Andres Smith
student
7836472098652
Mariah Anthony
dentist
Agora, tenho outro arquivo do qual extraio cerca de 10.000 padrões de chaves, como 1358726575123
. Então eu corro um for
loop com esses padrões e tenho que compará-los com o primeiro arquivo. Se o arquivo não contiver esse padrão, salve o padrão em um terceiro arquivo para processamento adicional:
for number in $(grep -o '[0-9]\{12\}' file2); do # finds about 10.000 keys
if ! grep -q ^$number$ file1; then # file1 is a huge file
printf "$number\n" >>file3 # we'll process file3 later
fi
done
O código de exemplo recebe um arquivo enorme 10.000 vezes e eu corro esse loop cerca de uma vez por minuto, durante o dia inteiro .
Como o enorme arquivo continua crescendo, o que posso fazer para acelerar tudo isso e economizar um pouco de CPU? Gostaria de saber se classificar o arquivo de alguma forma por sua chave (se sim, como?) Ou usar um db em vez de texto sem formatação ajudaria ...
Respostas:
Esta resposta é baseada na
awk
resposta postada por potong ..É duas vezes mais rápido que o
comm
método (no meu sistema), para os mesmos 6 milhões de linhas no arquivo principal e 10 mil chaves ... (agora atualizado para usar o FNR, NR)Embora
awk
seja mais rápido que o sistema atual e ofereça espaço para você e seu computador, lembre-se de que quando o processamento de dados for tão intenso quanto você descreveu, você obterá melhores resultados gerais ao mudar para um banco de dados dedicado; por exemplo. SQlite, MySQL ...fonte
file1 -> mainfile
efile2 -> keys
com gawk e mawk, e ele gera chaves erradas.awk
permitem que você leia em uma série de arquivos .. neste caso, essa série tem 3 arquivos nele a saída vai para.stdout
mainfile
, E que também irá imprimir todas as chaves dokeys
arquivo que são NÃO nomainfile
... Isso é provavelmente o que está acontecendo ... (eu vou olhar um pouco mais para ele ...$RANDOM
para o upload.O problema, é claro, é que você executa grep no grande arquivo 10.000 vezes. Você deve ler os dois arquivos apenas uma vez. Se você quiser ficar fora das linguagens de script, pode fazê-lo desta maneira:
comm
nas listas classificadas para obter o que há apenas na segunda listaAlgo assim:
Veja
man comm
.Se você pudesse truncar o arquivo grande todos os dias (como um arquivo de log), poderia manter um cache de números classificados e não precisaria analisá-lo todo o tempo.
fonte
{12}
.. OP usou 12, mas as chaves exemplo são 13 longo ...<(grep...sort)
onde estão os nomes dos arquivos.tail -n +$linenum
para gerar apenas os dados mais recentes. Dessa forma, você processará apenas aproximadamente 200.000 linhas por dia. Acabei de testá-lo com 6 milhões de linhas no arquivo principal e 10 mil chaves ... tempo : 0m0.016s reais, usuário 0m0.008s, sys 0m0.008sSim, definitivamente use um banco de dados. Eles são feitos exatamente para tarefas como esta.
fonte
Isso pode funcionar para você:
EDITAR:
O script alterado para permitir duplicatas e chaves desconhecidas nos dois arquivos ainda produz chaves do primeiro arquivo que não está presente no segundo:
fonte
Com tantos dados, você realmente deve mudar para um banco de dados. Enquanto isso, uma coisa que você deve fazer para chegar a um desempenho decente é não procurar
file1
separadamente cada chave. Execute um únicogrep
para extrair todas as chaves não excluídas de uma só vez. Como issogrep
também retorna linhas que não contêm uma chave, filtre-as.(
-Fx
significa pesquisar linhas inteiras, literalmente.-f -
significa ler uma lista de padrões a partir da entrada padrão.)fonte
-v
(-Fxv
) pode cuidar disso.comm
.Permita-me reforçar o que outros disseram: "Leve-o a um banco de dados!"
tem binários MySQL disponíveis gratuitamente para a maioria das plataformas.
Por que não o SQLite? É baseado em memória, carregando um arquivo simples quando você o inicia e depois fechá-lo quando terminar. Isso significa que, se o computador travar ou o processo SQLite desaparecer, todos os dados também desaparecerão.
Seu problema parece apenas algumas linhas de SQL e será executado em milissegundos!
Depois de instalar o MySQL (que eu recomendo em relação a outras opções), pagaria US $ 40 pelo SQL Cookbook da O'Reilly, de Anthony Molinaro, que tem muitos padrões de problemas, começando com
SELECT * FROM table
consultas simples e passando por agregações e várias junções.fonte
Não tenho certeza se esta é a saída exata que você está procurando, mas provavelmente a maneira mais fácil é:
Você também pode usar:
Cada um deles cria um arquivo de padrão temporário que é usado para coletar os números do arquivo grande (
file1
).fonte
grep -vf
vez degrep -f
.Concordo plenamente que você tenha um banco de dados (o MySQL é bastante fácil de usar). Até você começar a funcionar, eu gosto da
comm
solução da Angus , mas muitas pessoas estão tentandogrep
e errando que pensei em mostrar a (ou pelo menos uma) maneira correta de fazer issogrep
.O primeiro
grep
recebe as chaves. O terceirogrep
(no<(...)
) pega todas as chaves usadas no arquivo grande e o<(...)
passa como um arquivo como argumento para-f
o segundo grep. Isso faz com que o segundo ogrep
use como uma lista de linhas para corresponder. Ele então usa isso para corresponder à sua entrada (a lista de chaves) do canal (primeirogrep
) e imprime todas as chaves extraídas do arquivo de chaves e não (-v
) o arquivo grande.Claro que você pode fazer isso com arquivos temporários que você precisa acompanhar e lembre-se de excluir:
Isso imprime todas as linhas
allkeys
que não aparecemusedkeys
.fonte
grep: Memory exhausted
comm
, nessa ordem.O arquivo de chave não muda? Então você deve evitar procurar as entradas antigas repetidamente.
Com
tail -f
você pode obter a saída de um arquivo crescente.grep -f lê os padrões de um arquivo, uma linha como padrão.
fonte
Não postaria minha resposta porque achava que essa quantidade de dados não deveria ser processada com um script de shell, e a resposta certa para usar um banco de dados já foi dada. Mas desde agora existem 7 outras abordagens ...
Lê o primeiro arquivo na memória, depois recebe o segundo arquivo em busca de números e verifica se os valores estão armazenados na memória. Deve ser mais rápido que vários
grep
s, se você tiver memória suficiente para carregar o arquivo inteiro.fonte
Concordo com o @ jan-steinman que você deve usar um banco de dados para esse tipo de tarefa. Existem várias maneiras de hackear uma solução com um script de shell, como as outras respostas mostram, mas fazê-lo dessa maneira levará a muita miséria se você usar e manter o código por qualquer período de tempo maior que apenas um projeto descartável de um dia.
Supondo que você esteja em uma caixa Linux, é provável que você tenha o Python instalado por padrão, o que inclui a biblioteca sqlite3 a partir do Python v2.5. Você pode verificar sua versão do Python com:
Eu recomendo o uso da biblioteca sqlite3 porque é uma solução baseada em arquivo simples que existe para todas as plataformas (inclusive dentro do seu navegador da web!) E não requer a instalação de um servidor. Essencialmente com configuração zero e manutenção zero.
Abaixo está um script python simples que analisa o formato do arquivo que você deu como exemplo e, em seguida, faz uma consulta simples "selecionar tudo" e gera tudo o que é armazenado no banco de dados.
Sim, isso significa que você precisará aprender um pouco de SQL , mas valerá a pena a longo prazo. Além disso, em vez de analisar seus arquivos de log, talvez você possa gravar dados diretamente no seu banco de dados sqlite.
fonte
/usr/bin/sqlite3
funciona da mesma maneira para scripts de shell ( packages.debian.org/squeeze/sqlite3 ), embora eu nunca o tenha usado./usr/bin/sqlite3
com scripts de shell, no entanto, recomendo evitar scripts de shell, exceto programas simples de descarte e, em vez disso, use uma linguagem como python que tenha melhor tratamento de erros e seja mais fácil de manter e crescer.