Eu tenho um problema estranho com arquivos grandes e bash
. Este é o contexto:
- Eu tenho um arquivo grande: 75G e mais de 400.000.000 de linhas (é um arquivo de log, que pena, deixei crescer).
- Os 10 primeiros caracteres de cada linha são carimbos de hora no formato AAAA-MM-DD.
- Eu quero dividir esse arquivo: um arquivo por dia.
Eu tentei com o seguinte script que não funcionou. Minha pergunta é sobre esse script não funcionar, não soluções alternativas .
while read line; do
new_file=${line:0:10}_file.log
echo "$line" >> $new_file
done < file.log
Após a depuração, encontrei o problema na new_file
variável Este script:
while read line; do
new_file=${line:0:10}_file.log
echo $new_file
done < file.log | uniq -c
dá o resultado abaixo (coloquei x
es para manter os dados confidenciais, outros caracteres são reais). Observe as dh
seqüências de caracteres e as mais curtas:
...
27402 2011-xx-x4
27262 2011-xx-x5
22514 2011-xx-x6
17908 2011-xx-x7
...
3227382 2011-xx-x9
4474604 2011-xx-x0
1557680 2011-xx-x1
1 2011-xx-x2
3 2011-xx-x1
...
12 2011-xx-x1
1 2011-xx-dh
1 2011-xx-x1
1 208--
1 2011-xx-x1
1 2011-xx-dh
1 2011-xx-x1
...
Não é um problema no formato do meu arquivo . O script cut -c 1-10 file.log | uniq -c
fornece apenas carimbos de hora válidos. Curiosamente, uma parte da saída acima se torna com cut ... | uniq -c
:
3227382 2011-xx-x9
4474604 2011-xx-x0
5722027 2011-xx-x1
Podemos ver que, após a contagem uniq 4474604
, meu script inicial falhou.
Eu atingi um limite no bash que não conheço, encontrei um bug no bash (parece improvável) ou fiz algo errado?
Atualização :
O problema ocorre após a leitura de 2G do arquivo. Costuras read
e redirecionamento não gostam de arquivos maiores que 2G. Mas ainda procurando uma explicação mais precisa.
Update2 :
Definitivamente, parece um bug. Pode ser reproduzido com:
yes "0123456789abcdefghijklmnopqrs" | head -n 100000000 > file
while read line; do file=${line:0:10}; echo $file; done < file | uniq -c
mas isso funciona bem como uma solução alternativa (parece que eu achei um uso útil cat
):
cat file | while read line; do file=${line:0:10}; echo $file; done | uniq -c
Um bug foi arquivado no GNU e Debian. As versões afetadas são bash
4.1.5 no Debian Squeeze 6.0.2 e 6.0.4.
echo ${BASH_VERSINFO[@]}
4 1 5 1 release x86_64-pc-linux-gnu
Update3:
Graças a Andreas Schwab, que reagiu rapidamente ao meu relatório de erros, este é o patch que é a solução para esse mau comportamento. O arquivo impactado é lib/sh/zread.c
como Gilles apontou antes:
diff --git a/lib/sh/zread.c b/lib/sh/zread.c index 0fd1199..3731a41 100644
--- a/lib/sh/zread.c
+++ b/lib/sh/zread.c @@ -161,7 +161,7 @@ zsyncfd (fd)
int fd; { off_t off;
- int r;
+ off_t r;
off = lused - lind; r = 0;
A r
variável é usada para armazenar o valor de retorno de lseek
. Como lseek
retorna o deslocamento desde o início do arquivo, quando ele ultrapassa 2 GB, o int
valor é negativo, o que causa if (r >= 0)
falha no teste onde deveria ter êxito.
read
declaração em bash.Respostas:
Você encontrou um bug no bash, das sortes. É um bug conhecido com uma correção conhecida.
Programas representam um deslocamento em um arquivo como uma variável em algum tipo de número inteiro com um tamanho finito. Antigamente, todo mundo usava
int
praticamente tudo, e oint
tipo era limitado a 32 bits, incluindo o sinal, para armazenar valores de -2147483648 a 2147483647. Atualmente, existem nomes de tipos diferentes para coisas diferentes , inclusiveoff_t
para um deslocamento em um arquivo.Por padrão,
off_t
é um tipo de 32 bits em uma plataforma de 32 bits (permitindo até 2 GB) e um tipo de 64 bits em uma plataforma de 64 bits (permitindo até 8EB). No entanto, é comum compilar programas com a opção LARGEFILE, que muda o tipooff_t
para 64 bits de largura e faz com que o programa chame implementações adequadas de funções comolseek
.Parece que você está executando o bash em uma plataforma de 32 bits e o seu binário bash não é compilado com suporte a arquivos grandes. Agora, quando você lê uma linha de um arquivo regular, o bash usa um buffer interno para ler caracteres em lotes para desempenho (para obter mais detalhes, consulte a fonte em
builtins/read.def
). Quando a linha é concluída, o bash chamalseek
para retroceder o deslocamento do arquivo de volta à posição final da linha, caso algum outro programa se preocupe com a posição nesse arquivo. A chamada paralseek
acontece nazsyncfc
função emlib/sh/zread.c
.Não li a fonte com muitos detalhes, mas suponho que algo não esteja ocorrendo sem problemas no ponto de transição quando o deslocamento absoluto é negativo. Portanto, o bash acaba lendo as compensações erradas quando reabastece seu buffer, depois de ultrapassado a marca de 2 GB.
Se minha conclusão estiver errada e seu bash estiver de fato rodando em uma plataforma de 64 bits ou compilado com suporte a arquivos grandes, isso é definitivamente um bug. Por favor, reporte para sua distribuição ou upstream .
Um shell não é a ferramenta certa para processar arquivos tão grandes assim mesmo. Vai ser lento. Use sed, se possível, caso contrário, awk.
fonte
Não sei o que é errado, mas certamente é complicado. Se suas linhas de entrada estiverem assim:
Então não há realmente nenhuma razão para isso:
Você está fazendo muito trabalho de substring para acabar com algo que parece ... exatamente da maneira que já aparece no arquivo. Que tal agora?
Isso apenas pega os 10 primeiros caracteres da linha. Você também pode dispensar
bash
completamente e apenas usarawk
:$1
Ele pega a data (a primeira coluna delimitada por espaços em branco em cada linha) e a usa para gerar o nome do arquivo.Observe que é possível que haja algumas linhas de log falsas em seus arquivos. Ou seja, o problema pode estar na entrada, não no seu script. Você pode estender o
awk
script para sinalizar linhas falsas como esta:Isso grava as linhas correspondentes
YYYY-MM-DD
aos seus arquivos de log e sinaliza as linhas que não começam com um carimbo de data / hora no stdout.fonte
cut -c 1-10 file.log | uniq -c
fornece o resultado esperado. Estou usando${line:0:4}-${line:5:2}-${line:8:2}
porque colocarei o arquivo em um diretório${line:0:4}/${line:5:2}/${line:8:2}
e simplifiquei o problema (atualizarei a declaração do problema). Eu sei queawk
pode me ajudar aqui, mas eu tive outros problemas ao usá-lo. O que eu quero é entender o problemabash
e não encontrar soluções alternativas.cut
declaração que funciona. Como quero comparar maçãs com maçãs, não com laranjas, preciso tornar as coisas o mais parecidas possível.Parece que o que você quer fazer é:
A
close
mantém a tabela de arquivo aberto de encher.fonte