Remove linhas quase duplicadas

4

Eu tenho um problema complicado que não consigo resolver.

Eu tenho um arquivo de texto contendo alguns milhões de linhas de texto. Basicamente, quero executar uniq , mas com uma diferença: se duas linhas forem idênticas, mas para um sufixo :FOO , elimine a linha que não possui o sufixo. Mas somente se as linhas forem idênticas. E somente para :FOO , nenhum outro sufixo possível. não quer largar /usr/bin/delta:FOO , porque a linha acima não é idêntica.

red.7
green.2
green.2:FOO
blue.6
yellow.9:FOO

Eu quero excluir green.2 , porque a linha abaixo é idêntica, mas com um sufixo. Todas as outras linhas devem ser mantidas inalteradas.

[ Editar: Esqueci de mencionar que o arquivo já está em ordem de classificação.]

Meus pensamentos até agora:

  • Obviamente, uniq é a ferramenta para fazer isso.
  • Você pode fazer com que uniq ignore um prefixo , mas nunca um sufixo . (Isso é extremamente irritante!)
  • Pensei que você poderia fingir que : é um separador de campo e obtenha cut (junto com paste ) para inverter a ordem dos campos. Mas não, é aparentemente impossível forçar cut a produzir uma linha em branco se nenhum separador estiver presente.
  • Meu próximo pensamento é percorrer linha por linha e gerar um prefixo de 1 caractere, dependendo da presença ou ausência do sufixo ... mas não posso imaginá-lo como um loop de Bash com desempenho satisfatório.

Alguma dica?

Eu posso acabar usando apenas uma linguagem de programação real para corrigir isso. Parece simples o suficiente para consertar no Bash, mas eu já perdi muito tempo não conseguindo que funcionasse ...

    
por MathematicalOrchid 06.05.2016 / 14:31

3 respostas

3

Que tal juntar pares adjacentes de linhas e depois usar uma referência anterior para encontrar o prefixo não exclusivo?

$ sed '$!N; /\(.*\)\n:FOO/D; P;D' file
red.7
green.2:FOO
blue.6
yellow.9:FOO

Explicação:

  • $!N - se ainda não estivermos na última linha, anexe a próxima linha ao espaço padrão, separado por uma nova linha
  • /\(.*\)\n - corresponde tudo à nova linha (ou seja, o primeiro de cada par de linhas) e salva-o em um grupo de captura
  • :FOO agora corresponde ao que foi capturado na primeira linha, seguido por :FOO ( é uma referência anterior ao primeiro grupo de captura)
  • /\(.*\)\n:FOO/D - se a segunda linha de cada par for igual à primeira, seguida por :FOO , então D elete a primeira
  • P rint e D eletam a linha restante pronta para iniciar o próximo ciclo

ou mais puro (obrigado @don_crissti)

 sed '$!N; /\(.*\)\n:FOO/!P;D' file

N means there are always two consecutive lines in the pattern space and sed Prints the first one of them only if the second line isn't the same as the first one plus the suffix :FOO. Then D removes the first line from the pattern space and restarts the cycle.

    
por 06.05.2016 / 14:53
5

No caso mais simples, para manter as linhas sem :FOO , basta remover :FOO e passar pelo uniq:

$ sed 's/:FOO$//' file | uniq
red.7
green.2
blue.6
yellow.9

Se você preferir manter as linhas :FOO e supondo que elas sempre vêm depois de seus irmãos não-sufixados, tente:

$ rev file | sed 's/:/ /' | uniq -f1 | sed 's/ /:/' | rev
red.7
green.2:FOO
blue.6
yellow.9:FOO

rev imprime cada linha da direita para a esquerda. O sed substitui o primeiro : por um espaço, então uniq pode usar reconhecer FOO (ou OOF , nesse caso) como o primeiro campo que deve ser ignorado, o próximo sed coloca o : volta e o% final rev é impresso da esquerda para a direita novamente.

Infelizmente, e apesar do que a documentação diz, uniq não usa apenas espaço e tabulação como um delimitador de campo, mas praticamente qualquer caractere não alfanumérico:

$ printf 'foo/1\nfoo/2\nfoo%%3\nfoo:4\n' 
foo/1
foo/2
foo%3
foo:4
$ printf 'foo/1\nfoo/2\nfoo%%3\nfoo:4\n'  | uniq -f1
foo/1

Isso significa que a solução acima não funcionará se você tiver esses caracteres. Como alternativa, você poderia usar grep para todas as instâncias de :FOO em seu arquivo, remover o :FOO e alimentar o resultado para um novo grep como uma lista de padrões a serem evitados:

$ grep -hFxv "$(grep ':FOO' file | cut -d: -f1)" file 
red.7
green.2:FOO
blue.6
yellow.9:FOO
    
por 06.05.2016 / 14:57
5

Uma maneira em awk :

awk '$0 != x ":FOO" && NR>1 {print x} {x=$0} END {print}' file

Salva a linha e verifica no início de cada linha que não contém a string salva + :FOO . Imprimir a última linha, pois não é possível que a próxima linha tenha :FOO , pois não há nenhuma.

    
por 06.05.2016 / 16:18