Extrai linhas de um arquivo de texto com uma coluna igual ao máximo

2

Eu tenho um arquivo de texto (com linha de cabeçalho) chamado file.txt. Eu estou tentando extrair as linhas igual ao valor máximo de uma coluna específica (não sei qual é o valor máximo):

ID t1 q1 t2 q2 q3 
1 f 45 ex 1 45
2 r 47 tr 1 33
3 r 33 ex 2 44
4 f 44 s 0 55
5 e 32 ex 0 54
6 f 34 tr 2 46

Eu preciso encontrar o valor máximo da coluna $ 5 e, em seguida, imprimir apenas as linhas com a coluna 5 igual a este número:

3 r 33 ex 2 44
6 f 34 tr 2 46

Acho que o código a seguir funciona, mas meu arquivo é enorme e demora muito, então estou procurando uma solução mais rápida (talvez usando o tipo?):

Isso é o que eu tenho agora:

Encontre o valor máximo:

max='awk '{print $5}' file.txt | sort -nr | sed -n 2p'

Em seguida, selecione as linhas onde a coluna 5 é igual a este valor:

awk 'NR>1' file.txt|while read LINE; do value='echo $LINE|awk '{print $5}''; if [ $value -eq $max ]; then echo $LINE >> test.txt; fi; done
    
por user971102 10.09.2013 / 16:07

4 respostas

2

Uma maneira de fazer isso seria ler o arquivo uma vez para obter max e, em seguida, novamente para imprimir as linhas relevantes:

max=$(awk 'NR>1 && $5>max {max=$5}; END{print max}' file.txt) && 
 awk -v max="$max" '$5==max' file.txt 

Ou mais concisamente:

awk -v m="$(awk '(NR>1 && $5>m){m=$5};END{print m}' file.txt)"  '$5==m' file.txt 

O truque aqui é% flag -v do awk, que permite passar uma variável para awk . Nesse caso, primeiro calculo o valor máximo e, em seguida, atribuo a awk como variável max .

    
por 10.09.2013 / 16:15
1

Este é um problema bastante típico para o qual existe uma solução idiomática do awk envolvendo duas passagens sobre o arquivo. Na primeira passagem, identifique o valor máximo para $5 e, no segundo, extraia registros que contenham esse valor máximo em $5 . Aqui está um exemplo rápido.

awk 'NR == FNR && NR > 1{max = max < $5? $5: max; next}; $5 == max{print}' file.txt file.txt
    
por 10.09.2013 / 16:13
1

Se o uso de memória não for realmente um problema, uma versão de uma passagem no perl provavelmente seria:

perl -ane 'END { $"=""; print "@res"; } if($F[4] =~ /^\d+/ and $F[4] > $max) { 
    $max = $F[4]; @res = (); } push @res, $_ if($F[4] =~ /^\d+$/ and $max == $F[4]);' infile

-n diz ao perl para processar a linha infile one por vez passando cada linha para os comandos especificados em -e . O -a diz ao perl para expandir cada linha ao redor do separador de campos (o padrão é espaço) e atribui isso a uma matriz chamada @F . O resultado é para cada linha que podemos processá-lo e usar $F[n] para se referir ao enésimo elemento dessa linha.

O próprio perl:

END { $"=""; print "@res"; } # at the end of execution set the field separator to
                             # empty and print the contents of @res, which includes
                             # newlines when the matching rows were stored

if($F[4] =~ /^\d+/ and $F[4] > $max) {  # if the 5th element of the line is solely a
    $max = $F[4]; @res = ();            # number and it's greater than $max (which
}                                       # starts as undefined), set $max to this number
                                        # and empty the @res results array.

push @res, $_                                  # push this line to @res ...
     if($F[4] =~ /^\d+$/ and $max == $F[4]);   # IF the 5th element is solely a 
                                               # number and equal to $max

A lógica é que $max é iniciado como indefinido e a @res está vazia. Na primeira vez que encontramos um número na coluna 5, armazenamos isso em $max e vazio @res . Isso também tem o efeito colateral de esvaziar @res se encontrarmos um novo valor máximo na coluna 5 nas linhas a seguir. Como uma verificação separada, se a coluna 5 for igual a $max , adicione essa linha a @res (armazene linhas com o valor máximo atual). Repita para todas as linhas e, em seguida, o bloco END { } é executado, imprimindo o conteúdo do array @res results sem o separador de espaço de espaço principal normalmente definido em $" .

Isso provavelmente também pode ser alcançado em awk , mas meu awk-fu não é tão bom assim!

    
por 10.09.2013 / 23:25
1

Classifique o arquivo com os valores em ordem decrescente e, em seguida, imprima as linhas da parte superior até que o valor seja alterado.

sort -k 5n | awk 'NR==1 {max=$5} $5!=max {quit} {print}'
    
por 11.09.2013 / 03:33