![Processe grandes registros/parágrafos](https://rvso.com/image/178469/Processe%20grandes%20registros%2Fpar%C3%A1grafos.png)
Eu tenho um arquivo de texto grande (300 MB) com registros \n\n
como separador. Cada linha é um campo e começa com um número (a tag/nome do campo) seguido por um caractere TAB e o conteúdo/valor do campo:
110 something from record 1, field 110
149 something else
111 any field could be repeatable
111 any number of times
120 another field
107 something from record 2, field 107
149 fields could be repeatable
149 a lot of times
149 I mean a LOT!
130 another field
107 something from record 3
149 something else
Cada registro não deve ser maior que 100 KB.
Consegui encontrar alguns registros problemáticos (maiores que o limite)removendo finais de linha desses registros/"parágrafos"eobtendo seu comprimento:
cat records.txt | awk ' /^$/ { print; } /./ { printf("%s ", $0); } ' | awk '{print length+1}' | sort -rn | grep -P "^\d{6,}$"
Estou tentando encontrar uma maneira de processar esses registros inválidos:
- removendo registros maiores que o limite.
- removendo todas as ocorrências de uma tag problemática conhecida específica (149 no exemplo acima). É aceitável a hipótese de que nenhum registro ficará acima do limite se todas as linhas iniciadas pelo campo 149 forem removidas.
Provavelmente, remover ocorrências suficientes de um campo/tag específico para caber abaixo do limite merece um script completo. Seria ainda melhor remover primeiro os últimos.
Isso está relacionado a um antigo formato de arquivo de bibliotecário chamadoISO 2709.
Responder1
Se você quiser apenas pular os registros problemáticos:
awk 'BEGIN { ORS=RS="\n\n" } length <= 100*1000' file
Isso imprime cada registro que tenha menos ou igual a 100 mil caracteres.
Para excluir os campos que começam com um número inteiro positivo específico, se o registro for muito grande:
awk -v number=149 'BEGIN { ORS=RS="\n\n"; OFS=FS="\n" }
length <= 100*1000 { print; next }
{
# This is a too long record.
# Re-create it without any fields whose first tab-delimited
# sub-field is the number in the variable number.
# Split the record into an array of fields, a.
nf = split($0,a)
# Empty the record.
$0 = ""
# Go through the fields and add back the ones that we
# want to the output record.
for (i = 1; i <= nf; ++i) {
split(a[i],b,"\t")
if (b[1] != number) $(NF+1) = a[i]
}
# Print the output record.
print
}' file
Isso imprime registros curtos, como antes. Registros mais longos são removidos de todos os campos cujo primeiro subcampo delimitado por tabulação é o número number
(fornecido na linha de comando aqui como 149).
Para registros grandes, o registro é recriado sem os campos que não desejamos. O loop interno recria o registro de saída dividindo os campos em guias e anexando aqueles cujo primeiro subcampo delimitado por tabulação não é number
:
for (i = 1; i <= nf; ++i) {
split(a[i],b,"\t")
if (b[1] != number) $(NF+1) = a[i]
}
Como a especificação POSIX awk
deixa o que acontece quando você tem um valor de vários caracteres como RS
não especificado (a maioria das implementações o trata como uma expressão regular), você pode usar RS=""; ORS="\n\n"
em vez de ORS=RS="\n\n"
usar sua awk
implementação estritamente compatível. Se você fizer isso, observe que diversas linhas em branco nos dados não delimitariam mais registros vazios.
Responder2
Outra awk
abordagem:
awk -v lim=99999 'BEGIN{RS=""; ORS="\n\n"}\
{while (length()>=lim) {if (!sub(/\n149\t[^\n]*/,"")) break;}} length()<lim' file
Isto irá remover gradualmente as linhas começando com 149
se o comprimento do registro estiver acima do limite especificado na variável lim
, substituindo-as por "nada", até que o limite tenha sido mantido ou nenhuma outra redução seja possível (indicada pelo número de substituições reais sendo 0). Ele imprimirá apenas os registros cujo comprimento final for menor que o limite.
Desvantagem:Irá remover as 149
linhas a partir da primeira, pelo que se constituírem elementos individuais de texto contíguo, esse texto tornar-se-á um tanto incompreensível.
Observação:Especificar RS=""
em vez do explícito RS="\n\n"
é oportátilforma de usar awk
no "modo parágrafo", pois o comportamento de vários caracteres RS
não é definido pela especificação POSIX. Contudo, se puder havervazioregistros em seu arquivo, eles serão ignorados awk
e, conseqüentemente, não aparecerão na saída. Se não for isso que você deseja, talvez seja necessário usar a RS="\n\n"
notação explícita - a maioria awk
das implementações a tratará como uma expressão regular e fará o que seria "ingenuamente" esperado.
Responder3
Sempre que você tiver \n\n
como separador de registros, pense no modo Perl e parágrafo (de man perlrun
):
-0[octal/hexadecimal]
specifies the input record separator ($/) as an octal or hexadecimal number.
[...]
The special value 00 will cause Perl to slurp files in paragraph mode.
Usando isso, você pode fazer:
Remova todos os registros com mais de 100.000personagens(observe que isso pode não ser o mesmo que bytes, dependendo da codificação do seu arquivo):
perl -00 -ne 'print unless length()>100000' file
Corte todos os registros com mais de 100.000 caracteres removendo todos os caracteres após os primeiros 100.000:
perl -00 -lne 'print substr($_,0,100000)' file
Remova as linhas que começam com
149
:perl -00 -pe 's/(^|\n)149\s+[^\n]+//g;' file
Remova as linhas que começam com
149
mas somente se esse registro tiver mais de 100.000 caracteres:perl -00 -pe 's/(^|\n)149\s+[^\n]+//g if length()>100000; ' file
Se um registro tiver mais de 100.000 caracteres, remova as linhas que começam com
149
até que o registro tenha menos de 100.000 caracteres ou até que não haja mais linhas com 149:perl -00 -pe 'while(length()>100000 && /(^|\n)149\s/){s/(^|\n)149\s+[^\n]+//}' file
Se um registro tiver mais de 100.000 caracteres, remova as linhas começando com
149
até que o registro tenha menos de 100.000 caracteres ou não haja mais linhas com 149, e se foraindacom mais de 100.000 caracteres, imprima apenas os primeiros 100.000:perl -00 -lne 'while(length()>100000 && /(^|\n)149\s/){ s/(^|\n)149\s+[^\n]+// } print substr($_,0,100000)' file
Finalmente, como acima, mas remova linhas inteiras, não apenas caracteres, até obter o tamanho certo para não ter registros truncados:
perl -00 -ne 'while(length()>100000 && /(^|\n)149\s/){ s/(^|\n)149\s+[^\n]+// } map{ $out.="$_\n" if length($out . "\n$_")<=100000 }split(/\n/); print "$out\n"; $out="";' file
Responder4
Provavelmente poderia ser mais elegante, mas aqui está uma solução:
cat records.txt | awk -v RS='' '{if (length>99999) {gsub(/\n149\t[^\n]*\n/,"\n");print $0"\n"} else {print $0"\n"} }'
Estou ciente do uso inútil do gato, acreditoé mais claro o fluxo da esquerda para a direita.
Onde 99999 é o tamanho do limite e 149 o início da linha (nome do campo) a ser removido nesse caso.
Eu uso um não ganancioso \n149\t[^\n]*\n/
para remover apenas o que seria ^149\t.*$
.
gsub
substitui o padrão pela string especificada e retorna o número de substituições/substituições feitas.
Foi inspirado emesta resposta.