Uma solução awk
vista em #bash (Freenode):
awk '!seen[$0]++' filename
Um arquivo de texto enorme (até 2 GiB) meu contém cerca de 100 duplicatas exatas de cada linha nele (inútil no meu caso, já que 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 por um aumento significativo no desempenho) mantendo a ordem original da sequência. No resultado, cada linha deve ser única. Se houver 100 linhas iguais (geralmente as duplicatas estão espalhadas pelo arquivo e não serão vizinhas), haverá apenas uma do tipo restante.
Eu escrevi um programa no Scala (considere Java se você não sabe sobre o Scala) para implementar isso. Mas talvez haja ferramentas nativas mais rápidas, escritas em C, capazes de fazer isso mais rápido?
UPDATE: a solução awk '!seen[$0]++' filename
parecia estar funcionando bem para mim, desde que os arquivos estivessem perto de 2 GiB ou menores, mas agora, como estou limpando um arquivo de 8 GiB, ele não funciona mais. Parece tomar infinito em um Mac com 4 GiB de RAM e um Windows 7 de 64 bits com 4 GiB de RAM e 6 GiB swap apenas fica sem memória. E eu não me sinto entusiasmado em testá-lo no Linux com 4 GiB de RAM, dada essa experiência.
Existe um método simples (que não é óbvio) usando utilitários padrão que não requerem uma grande memória, exceto executar sort
, que na maioria das implementações possui otimizações específicas para arquivos grandes (um bom algoritmo de ordenação externa) . Uma vantagem deste método é que ele apenas faz um loop em 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ê poderá 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 melhor desempenho. 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}
, isto é, imprime a linha atual se ela ainda não foi vista, e então incrementa o contador seen
desta linha (variáveis não inicializadas ou elementos de array 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 uma sobrecarga constante por linha. Mas a computação digere é bastante lenta; este método só ganhará se você tiver uma CPU rápida (especialmente uma com um acelerador de hardware para computar as compilações) e não muita memória relativa 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
sort -u big-csv-file.csv > duplicates-removed.csv
Observe que o arquivo de saída será classificado.
Supondo que você pode manter tanto arquivo duplicado na memória (se seus dados forem de fato duplicados por um fator de 100, isso deve ser cerca de 20MiB +), 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 hash %dup
, se desejar, como um bônus adicional.
Se você preferir awk
, isso também deve ser feito (mesma lógica da versão perl, mesma ordem, mesmos dados reunidos na variável dup
):
$ awk '{if (++dup[$0] == 1) print $0;}' input_file > output_file
Como nenhuma outra resposta forneceu suporte, aqui está um:
gawk -i inplace '!a[$0]++' file
Liners do Python One:
python -c "import sys; lines = sys.stdin.readlines(); print ''.join(sorted(set(lines)))" < InputFile
Nenhuma das respostas aqui funcionou para mim no meu Mac, então escrevi um script Python simples que funciona para mim. Eu estou ignorando o espaço em branco inicial / final 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 acima em unique.py e corra assim:
python unique.py inputfile.txt outputfile.txt
Com o bash 4, uma solução pura que tira vantagem de arrays associativos 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