Existe uma maneira de excluir linhas duplicadas em um arquivo no Unix?
Eu posso fazer isso com sort -u
e uniq
comandos, mas quero usar sed
ou awk
. Isso é possível?
awk
, mas consumirá bastante recursos em arquivos maiores.
Existe uma maneira de excluir linhas duplicadas em um arquivo no Unix?
Eu posso fazer isso com sort -u
e uniq
comandos, mas quero usar sed
ou awk
. Isso é possível?
awk
, mas consumirá bastante recursos em arquivos maiores.
Respostas:
awk '!seen[$0]++' file.txt
seen
é uma matriz associativa para a qual o Awk passará todas as linhas do arquivo. Se uma linha não estiver na matriz, ela seen[$0]
será avaliada como falsa. O !
operador NOT é lógico e inverte o falso para verdadeiro. O Awk imprimirá as linhas em que a expressão é avaliada como verdadeira. Os ++
incrementos seen
para que, seen[$0] == 1
após a primeira vez que uma linha seja encontrada seen[$0] == 2
, e assim por diante.
O Awk avalia tudo menos 0
e ""
(string vazia) como true. Se uma linha duplicada for inserida, seen
ela !seen[$0]
será avaliada como falsa e a linha não será gravada na saída.
awk '!seen[$0]++' merge_all.txt > output.txt
for f in *.txt; do gawk -i inplace '!seen[$0]++' "$f"; done
Em http://sed.sourceforge.net/sed1line.txt : (Por favor, não me pergunte como isso funciona ;-))
# delete duplicate, consecutive lines from a file (emulates "uniq").
# First line in a set of duplicate lines is kept, rest are deleted.
sed '$!N; /^\(.*\)\n\1$/!P; D'
# delete duplicate, nonconsecutive lines from a file. Beware not to
# overflow the buffer size of the hold space, or else use GNU sed.
sed -n 'G; s/\n/&&/; /^\([ -~]*\n\).*\n\1/d; s/\n//; h; P'
$!
peça é necessária? Não sed 'N; /^\(.*\)\n\1$/!P; D'
faz a mesma coisa? Não consigo criar um exemplo em que os dois sejam diferentes na minha máquina (fwiw, tentei uma linha vazia no final com as duas versões e ambas estavam bem).
[ -~]
representa um intervalo de caracteres ASCII de 0x20 (espaço) a 0x7E (til). Esses são considerados os caracteres ASCII imprimíveis (a página vinculada também possui 0x7F / delete, mas isso não parece correto). Isso faz com que a solução seja quebrada para quem não usa ASCII ou para quem digita, por exemplo, caracteres de tabulação. O mais portátil [^\n]
inclui muito mais caracteres ... todos, exceto um, de fato.
One-liner Perl semelhante à solução awk de @ jonas:
perl -ne 'print if ! $x{$_}++' file
Essa variação remove o espaço em branco à direita antes de comparar:
perl -lne 's/\s*$//; print if ! $x{$_}++' file
Essa variação edita o arquivo no local:
perl -i -ne 'print if ! $x{$_}++' file
Essa variação edita o arquivo no local e faz um backup file.bak
perl -i.bak -ne 'print if ! $x{$_}++' file
A linha única que Andre Miller postou acima funciona, exceto nas versões recentes do sed quando o arquivo de entrada termina com uma linha em branco e sem caracteres. No meu Mac, minha CPU gira.
Loop infinito se a última linha estiver em branco e não tiver caracteres :
sed '$!N; /^\(.*\)\n\1$/!P; D'
Não trava, mas você perde a última linha
sed '$d;N; /^\(.*\)\n\1$/!P; D'
A explicação está no final da FAQ sed :
O mantenedor do GNU sed considerou que, apesar dos problemas de portabilidade que
isso causaria, alterar o comando N para imprimir (em vez de
excluir) o espaço do padrão era mais consistente com as intuições de alguém
sobre como deveria se comportar um comando para "acrescentar a próxima linha" .
Outro fato favorável à mudança foi que "{N; command;}"
excluirá a última linha se o arquivo tiver um número ímpar de linhas, mas
imprimirá a última linha se o arquivo tiver um número par de linhas.Para converter scripts que usavam o antigo comportamento de N (excluindo
o espaço do padrão ao atingir o EOF) em scripts compatíveis com
todas as versões do sed, altere um "N" isolado; para "$ d; N;" .
$ echo -e '1\n2\n2\n3\n3\n3\n4\n4\n4\n4\n5' |sed -nr '$!N;/^(.*)\n\1$/!P;D'
1
2
3
4
5
a ideia central é:
print ONLY once of each duplicate consecutive lines at its LAST appearance and use D command to implement LOOP.
Explica:
$!N;
: se a linha atual NÃO for a última, use N
comando para ler a próxima linha pattern space
./^(.*)\n\1$/!P
: se o conteúdo da corrente pattern space
é dois duplicate string
separados por \n
, o que significa que a próxima linha é a same
linha com corrente, NÃO podemos imprimi-lo de acordo com nossa idéia principal; caso contrário, o que significa que a linha atual é a ÚLTIMA aparência de todas as suas linhas consecutivas duplicadas, agora podemos usar o P
comando para imprimir os caracteres no pattern space
utilitário atual \n
(\n
também impresso).D
: usamos o D
comando para excluir os caracteres no pattern space
utilitário atual \n
(\n
também excluído), e o conteúdo de pattern space
é a próxima linha.D
comando forçará sed
a pular para seu FIRST
comando$!N
, mas NÃO lê a próxima linha do arquivo ou fluxo de entrada padrão.$ echo -e '1\n2\n2\n3\n3\n3\n4\n4\n4\n4\n5' |sed -nr 'p;:loop;$!N;s/^(.*)\n\1$/\1/;tloop;D'
1
2
3
4
5
a ideia central é:
print ONLY once of each duplicate consecutive lines at its FIRST appearance and use : command & t command to implement LOOP.
Explica:
:loop
comando set a label
named loop
.N
para ler a próxima linha no pattern space
.s/^(.*)\n\1$/\1/
para excluir a linha atual se a próxima linha for a mesma da linha atual, usamos o s
comando para delete
executar a ação.s
comando for executado com sucesso, use a tloop
força de comando sed
para pular para o label
nomeado loop
, que fará o mesmo loop para as próximas linhas util; não há linhas consecutivas duplicadas da linha que é latest printed
; caso contrário, use o D
comando para delete
a linha que é a mesma com o latest-printed line
e force sed
para pular para o primeiro comando, que é o p
comando, o conteúdo de current pattern space
é a próxima nova linha.busybox echo -e "1\n2\n2\n3\n3\n3\n4\n4\n4\n4\n5" | busybox sed -nr "$!N;/^(.*)\n\1$/!P;D"
cat filename | sort | uniq -c | awk -F" " '$1<2 {print $2}'
Exclui as linhas duplicadas usando o awk.
cat
é inútil. De qualquer forma, uniq
já faz isso por si só e não exige que a entrada seja exatamente uma palavra por linha.
uniq
só isso é suficiente.