Como remover linhas duplicadas dentro de um arquivo de texto?

Um enorme arquivo de texto (até 2 GiB) meu contém cerca de 100 duplicatas exatas de cada linha nele (inútil no meu caso, pois o arquivo é uma tabela de dados do tipo CSV).

O que eu preciso é remover todas as repetições enquanto (de preferência, mas isso pode ser sacrificado para um aumento significativo de desempenho) mantendo a ordem da sequência original. No resultado, cada linha deve ser única. Se houvesse 100 linhas iguais (geralmente as duplicatas estão espalhadas pelo arquivo e não serão vizinhas), deve haver apenas uma do tipo restante.

Eu escrevi um programa em Scala (considere-o Java, se você não conhece Scala) para implementar isso. Mas talvez existam ferramentas nativas escritas em C mais rápidas capazes de fazer isso mais rápido?

ATUALIZAÇÃO: a solução awk "!seen[$0]++" filename parecia funcionar muito bem para mim, desde que os arquivos estavam perto de 2 GiB ou menores, mas agora como devo limpar um arquivo de 8 GiB, ele não funciona mais. Parece que está levando o infinito em um Mac com 4 GiB de RAM e um PC Windows 7 de 64 bits com 4 GiB de RAM e a troca de 6 GiB simplesmente fica sem memória. E não me sinto entusiasmado em tentar no Linux com 4 GiB de RAM dada esta experiência.

Comentários

  • isso irá destruir seu pedido, mas, você já tentou sort -u, não tenho ideia de como ou se ele pode ser executado em um arquivo tão grande
  • C frequentemente não é significativamente mais rápido que Java, e se você ‘ reexecutá-lo (em ordem) agora, há ‘ uma chance razoável de ‘ terminará antes que você obtenha uma resposta aqui, implemente-a e a execução será concluída; fora de ordem, sort -u provavelmente será mais rápido.

Resposta

Uma solução awk vista em #bash (Freenode):

awk "!seen[$0]++" filename 

Comentários

  • Tentei fazer isso em um arquivo 2G e demorou três minutos no meu notebook. Nada mal. Eu também tentei uniq filename | awk ‘! visto [$ 0] ++ ‘, mas não foi ‘ qualquer mais rápido.
  • @HashWizard: este comando não classifica, mas elimina todas as próximas ocorrências da mesma linha
  • Quer saber como este comando funciona? – Veja aqui: unix.stackexchange.com/questions/159695/how-does-awk-a0-work
  • @MaxWilliams sim , funciona se eles são distribuídos aleatoriamente.
  • preserve novas linhas ou linhas com espaços awk '/^\s*?$/||!seen[$0]++'

Resposta

Há um método simples (o que não quer dizer óbvio) usando utilitários padrão que não requer muita memória, exceto para executar sort, que na maioria das implementações tem otimizações específicas para arquivos grandes (um bom algoritmo de ordenação externa). Uma vantagem deste método é que ele apenas percorre todas as linhas dentro de utilitários de propósito especial, nunca dentro de linguagens interpretadas.

<input nl -b a -s : | # number the lines sort -t : -k 2 -u | # sort and uniquify ignoring the line numbers sort -t : -k 1n | # sort according to the line numbers cut -d : -f 2- >output # remove the line numbers 

Se todas as linhas começarem com um caractere sem espaço em branco, você pode dispensar algumas das opções:

<input nl | sort -k 2 -u | sort -k 1n | cut -f 2- >output 

Para uma grande quantidade de duplicação, um método que requer apenas o armazenamento de uma única cópia de cada linha na memória terá um desempenho melhor. Com alguma sobrecarga de interpretação, há “um script awk muito conciso para isso (já postado por enzotib ):

<input awk "!seen[$0]++" 

Menos concisamente: !seen[$0] {print} {seen[$0] += 1}, ou seja, imprime a linha atual se ainda não tiver sido vista e, em seguida, incremente seen contador para esta linha (variáveis não inicializadas ou elementos de matriz têm o valor numérico 0).

Para linhas longas, você pode economizar memória mantendo apenas uma soma de verificação não falsificável (por exemplo, um resumo criptográfico) de cada linha . Por exemplo, usando SHA-1, você só precisa de 20 bytes mais um overhead constante por linha. Mas o resumo da computação é bastante lento; este método só vencerá se você tiver uma CPU rápida (especialmente uma com um acelerador de hardware para calcular os resumos) e não muita memória em relação ao tamanho do arquivo e linhas suficientemente longas. Nenhum utilitário básico permite calcular uma soma de verificação para cada linha; você teria que suportar a sobrecarga de interpretação de Perl / Python / Ruby /… ou escrever um programa compilado dedicado.

<input perl -MDigest::MD5 -ne "$seen{Digest::MD5::md5($_)}++ or print" >output 

Comentários

  • @Gilles Baseado em sua explicação de awk '!seen[$0]++', isso significa que se awk vir 2 linhas duplicadas, ele manterá sempre a primeira e ignorará todas as subsequentes? (Ou manterá a última?)
  • @ user779159 Mantém a primeira: cada linha de entrada é impressa imediatamente (primeira ocorrência) ou não é impressa (repetição da ocorrência).
  • Mas como isso se compara a classificar -u …?
  • @HashWizard Um simples sort -u altera a ordem.Minha resposta mostra soluções que preservam a ordem (a ordem das primeiras ocorrências, para ser mais preciso).
  • @Gilles você diria que é mais rápido do que classificar -u para arquivos grandes (10G) com 50% de duplicatas ?

Resposta

sort -u big-csv-file.csv > duplicates-removed.csv 

Observe que o arquivo de saída irá ser classificado.

Comentários

  • Não tão rápido quanto o comando awk em outras respostas, mas conceitualmente simples!
  • @Johann Estou fazendo isso com bastante frequência em arquivos com centenas de milhares (até milhões) de strings curtas terminadas em nova linha. Eu obtenho os resultados bem rápido para os experimentos que estou fazendo. Pode ser mais importante se usado em scripts que são executados repetidamente, a economia de tempo pode ser considerável.
  • Use sort -u para remover duplicatas durante a classificação, em vez de depois. (E economiza largura de banda de memória) canalizando-o para outro programa). Isso só é melhor do que a versão awk se você quiser que sua saída também seja classificada. (O OP nesta questão deseja que sua ordem original preservada , então esta é uma boa resposta para um caso de uso ligeiramente diferente.)
  • Demorou cerca de um minuto, para mim, para um arquivo de 5,5 milhões de linhas (1,8 GB no total). Brilhante.

Resposta

Presumindo que você possa manter tanto quanto o arquivo desduplicado na memória ( se os seus dados forem realmente duplicados por um fator de 100, que deve ser cerca de 20 MiB + overhead), você pode fazer isso facilmente com Perl.

$ perl -ne "print unless $dup{$_}++;" input_file > output_file 

Isso preserva a ordem também.

Você pode extrair o número de ocorrências de cada linha do %dup hash se desejar, como um bônus grátis adicional.

Se você preferir awk, isso também deve servir (mesma lógica da versão perl, mesma ordem, mesmos dados reunidos em dup variável):

$ awk "{if (++dup[$0] == 1) print $0;}" input_file > output_file 

Comentários

  • Isso é muito bom @Mat, I estava prestes a slurp o arquivo, lol ;-).
  • Agora, esperando por @ManAtWork para sua magia de tecer sed e awk também 🙂
  • incrível de novo para a dica do awk: – )
  • É possível alterar o script perl para apenas remover e linhas adjacentes duplicadas?
  • @dumbledad: uniq faz isso sozinho

Resposta

Como nenhuma outra resposta forneceu suporte local, aqui está uma:

gawk -i inplace "!a[$0]++" file 

Comentários

  • Isso preserva a ordem? A propósito, isso não funcionou para mim. Minha versão é: GNU Awk 4.0.2
  • @Leonid sim, é verdade. Ele imprime a primeira ocorrência de qualquer linha exclusiva. O suporte local foi introduzido pela primeira vez na versão 4.1, lançada em 2013.
  • Esta deve ser a resposta. Ele ‘ realmente exclui a string duplicada no arquivo existente ou atual, onde a resposta principal e a maioria das respostas aqui apenas imprime as strings uniq / duplicadas e não faz nada e temos que criar outra saída para armazenar o resultado.

Resposta

Você pode usar uniq http://www.computerhope.com/unix/uuniq.htm

uniq relata ou filtra linhas repetidas em um arquivo.

Comentários

  • Ao dar uma resposta, é preferível dar alguma explicação de POR QUE sua resposta é essa. Então, como esta resposta difere de várias das respostas anteriores?
  • Da página de manual do uniq: Observação: 'uniq' does not detect repeated lines unless they are adjacent. Então você tem que primeiro classificá-la e soltar a ordem das linhas não duplicadas.

Resposta

Python One liners:

python -c "import sys; lines = sys.stdin.readlines(); print "".join(sorted(set(lines)))" < InputFile 

Comentários

  • isso faz com que todo o arquivo seja inserido na memória e pode não ser adequado para o problema do OP ‘ s. Também não é garantido manter a ordem
  • Obrigado pela sugestão, eu ‘ estive apenas aprendendo Python .. apenas tentei isso para fins de aprendizagem ..:)
  • Aqui ‘ s uma versão Python 2.7 que não é de uma linha, mas (sucintamente) retorna linhas exclusivas preservando a ordem sem carregar o arquivo inteiro na memória ou criar uma única string gigante para alimentar para impressão
  • Obrigado @ 1_CR, tenho algo a aprender hoje 🙂 OrderedDict

Resposta

Nenhuma das respostas aqui funcionou para mim no meu Mac, então escrevi um python simples script que funciona para mim. Estou ignorando os espaços em branco à esquerda / à direita e também não me importo com o consumo de memória.

import sys inputfile = sys.argv[1] outputfile = sys.argv[2] with open(inputfile) as f: content = f.readlines() content = [x.strip() for x in content] my_list = list(set(content)) with open(outputfile, "w") as output: for item in my_list: output.write("%s\n" % item) 

Salve o texto acima como exclusivo.py e execute assim:

python unique.py inputfile.txt outputfile.txt 

Resposta

SOLUÇÃO SEM MANTER A ORDEM DA SEQUÊNCIA ORIGINAL

Fiz isso com a seguinte peça de código.

sort duplicates.txt | uniq > noDuplicates.txt 

O comando sort classifica as linhas em ordem alfabética e o comando uniq remove as duplicatas.

NOTA: Porque classificamos as linhas primeiro é que uniq não detecta linhas duplicadas, a menos que sejam adjacentes.

Comentários

  • A questão pede um método (de preferência ) que mantém a ordem de entrada; você poderia editar sua resposta para resolver isso? Observe que existem respostas usando sort que mantém a ordem de entrada e uma resposta usando sort sem manter a ordem de entrada, mas de maneira mais eficiente do que canalizar para uniq.
  • @StephenKitt Editado. Verifiquei outras respostas, mas não consegui ‘ encontrar nada apenas com os comandos básicos. Obrigado por seus comentários.
  • Eu dei a você um link para uma resposta com apenas comandos básicos, na verdade apenas um comando, sort -u (que faz parte do POSIX ) ;-).
  • @StephenKitt Eu vi essa resposta. A minha também é uma forma de lidar com o problema. O que você quer que eu faça mais? Devo excluir a resposta?
  • Não, não exclua sua resposta; Eu só queria ter certeza de que você estava ciente da outra resposta, já que você disse que “não poderia ‘ encontrar nada apenas com comandos básicos”.

Resposta

Com o bash 4, uma solução de bash puro que tira proveito de matrizes associativas pode ser usado. Aqui está um exemplo

unset llist; declare -A llist; while read -r line; do if [[ ${llist[$line]} ]]; then continue else printf "%s\n" "$line" llist[$line]="x" fi done < file.txt 

Comentários

  • Não ‘ t use read loops para processar arquivos de texto grandes. bash tem que ler um byte de cada vez para evitar ultrapassar uma nova linha. O Bash também não é muito rápido no processamento de texto em geral se comparado ao awk. Se você usar isso, read -ra evitará barras invertidas em sua entrada. Além disso, não ‘ se esqueça de unset llist depois do loop, se você colocar isso em uma função shell ou use-o interativamente.
  • @PeterCordes, ou você poderia apenas ter mencionado isso 🙂

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *