Processe grandes registros/parágrafos

Processe grandes registros/parágrafos

Eu tenho um arquivo de texto grande (300 MB) com registros \n\ncomo 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 awkdeixa o que acontece quando você tem um valor de vários caracteres como RSnã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 awkimplementação estritamente compatível. Se você fizer isso, observe que diversas linhas em branco nos dados não delimitariam mais registros vazios.

Responder2

Outra awkabordagem:

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 149se 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 149linhas 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 awkno "modo parágrafo", pois o comportamento de vários caracteres RSnão é definido pela especificação POSIX. Contudo, se puder havervazioregistros em seu arquivo, eles serão ignorados awke, 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 awkdas implementações a tratará como uma expressão regular e fará o que seria "ingenuamente" esperado.

Responder3

Sempre que você tiver \n\ncomo 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:

  1. 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
    
  2. 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
    
  3. Remova as linhas que começam com 149:

     perl -00 -pe 's/(^|\n)149\s+[^\n]+//g;' file
    
  4. Remova as linhas que começam com 149mas somente se esse registro tiver mais de 100.000 caracteres:

     perl -00 -pe 's/(^|\n)149\s+[^\n]+//g if length()>100000; ' file
    
  5. Se um registro tiver mais de 100.000 caracteres, remova as linhas que começam com 149até 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
    
  6. Se um registro tiver mais de 100.000 caracteres, remova as linhas começando com 149até 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
    
  7. 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.*$.

gsubsubstitui o padrão pela string especificada e retorna o número de substituições/substituições feitas.

Foi inspirado emesta resposta.

informação relacionada