Identifica linhas duplicadas em um arquivo sem excluí-las?


11

Eu tenho minhas referências como um arquivo de texto com uma longa lista de entradas e cada uma possui dois (ou mais) campos.

A primeira coluna é o URL da referência; a segunda coluna é o título que pode variar um pouco, dependendo de como a entrada foi feita. O mesmo para o terceiro campo que pode ou não estar presente.

Quero identificar, mas não remover, entradas que tenham o primeiro campo (URL de referência) idêntico. Eu sei, sort -k1,1 -umas isso removerá automaticamente (sem interatividade) todos, exceto o primeiro hit. Existe uma maneira de me informar para que eu possa escolher qual manter?

No extrato abaixo de três linhas que possuem o mesmo primeiro campo ( http://unix.stackexchange.com/questions/49569/), eu gostaria de manter a linha 2 porque ela possui tags adicionais (classificar, CLI) e excluir as linhas 1 e 3:

http://unix.stackexchange.com/questions/49569/  unique-lines-based-on-the-first-field
http://unix.stackexchange.com/questions/49569/  Unique lines based on the first field   sort, CLI
http://unix.stackexchange.com/questions/49569/  Unique lines based on the first field

Existe um programa para ajudar a identificar essas "duplicatas"? Então, eu posso limpar manualmente excluindo pessoalmente as linhas 1 e 3?


Não entendo bem o seu exemplo ... você poderia fornecer uma versão mais simplificada da entrada e da saída esperada?
Oli

Por favor, veja se está mais claro agora?
DK Bose

Respostas:


9

Se entendi sua pergunta, acho que você precisa de algo como:

for dup in $(sort -k1,1 -u file.txt | cut -d' ' -f1); do grep -n -- "$dup" file.txt; done

ou:

for dup in $(cut -d " " -f1 file.txt | uniq -d); do grep -n -- "$dup" file.txt; done

onde file.txtestá o seu arquivo que contém dados sobre você?

Na saída, você verá o número de linhas e linhas em que o primeiro campo é encontrado duas ou mais vezes.


3
Obrigado: até cut -d " " -f1 file.txt | uniq -dme dá uma boa saída.
DK Bose

@DKBose Provavelmente existem mais possibilidades, mas eu queria usar e seu comando também.
Radu Rădeanu 15/03

Obrigado. O segundo comando é o que eu gosto. Você pode remover o primeiro. E se você explicar o código que seria bom também :)
DK Bose

10

Este é um problema clássico que pode ser resolvido com o uniqcomando uniqpode detectar linhas consecutivas duplicadas e remover duplicatas ( -u, --unique) ou manter apenas duplicatas ( -d, --repeated).

Como a ordem de linhas duplicadas não é importante para você, você deve classificá-las primeiro. Em seguida, use uniqpara imprimir apenas linhas exclusivas:

sort yourfile.txt | uniq -u

Há também uma opção -c( --count) que imprime o número de duplicatas para a -dopção. Veja a página de manual de uniqpara detalhes.


Se você realmente não se importa com as peças após o primeiro campo, pode usar o seguinte comando para encontrar chaves duplicadas e imprimir cada número de linha para ela (acrescente outro | sort -npara que a saída seja classificada por linha):

 cut -d ' ' -f1 .bash_history | nl | sort -k2 | uniq -s8 -D

Como você deseja ver linhas duplicadas (usando o primeiro campo como chave), não é possível usar diretamente uniq. O problema que dificulta a automação é que as partes do título variam, mas um programa não pode determinar automaticamente qual título deve ser considerado o final.

Aqui está um script AWK (salve-o em script.awk) que usa seu arquivo de texto como entrada e imprime todas as linhas duplicadas para que você possa decidir qual excluir. ( awk -f script.awk yourfile.txt)

#!/usr/bin/awk -f
{
    # Store the line ($0) grouped per URL ($1) with line number (NR) as key
    lines[$1][NR] = $0;
}
END {
    for (url in lines) {
        # find lines that have the URL occur multiple times
        if (length(lines[url]) > 1) {
            for (lineno in lines[url]) {
                # Print duplicate line for decision purposes
                print lines[url][lineno];
                # Alternative: print line number and line
                #print lineno, lines[url][lineno];
            }
        }
    }
}

Eu acho que isso está próximo do que eu quero, mas preciso do oposto de `-f, --skip-fields = N (evite comparar os primeiros N campos). Em outras palavras, quero que apenas o primeiro campo, os URLs, sejam considerados.
DK Bose

@DKBose Existe uma opção -w( --check-chars) para limitar a um número fixo de caracteres, mas, vendo o seu exemplo, você tem os primeiros campos variáveis. Como uniqnão oferece suporte à seleção de campo, é necessário usar uma solução alternativa. Vou incluir um exemplo do AWK, já que é mais fácil.
Lekensteyn

Sim, eu estava apenas olhando, -wmas o comprimento do primeiro campo é variável :(
DK Bose

@DKBose Por favor, veja a edição mais recente #
Lekensteyn 15/03

1
Estou obtendo awk: script.awk: linha 4: erro de sintaxe em ou próximo [awk: script.awk: linha 10: erro de sintaxe em ou próximo [awk: script.awk: linha 18: erro de sintaxe em ou próximo}
DK Bose

2

Se eu li isso corretamente, tudo que você precisa é algo como

awk '{print $1}' file | sort | uniq -c | 
    while read num dupe; do [[ $num > 1 ]] && grep -n -- "$dupe" file; done

Isso imprimirá o número da linha que contém o dupe e a própria linha. Por exemplo, usando este arquivo:

foo bar baz
http://unix.stackexchange.com/questions/49569/  unique-lines-based-on-the-first-field
bar foo baz
http://unix.stackexchange.com/questions/49569/  Unique lines based on the first field   sort, CLI
baz foo bar
http://unix.stackexchange.com/questions/49569/  Unique lines based on the first field

Isso produzirá esta saída:

2:http://unix.stackexchange.com/questions/49569/  unique-lines-based-on-the-first-field
4:http://unix.stackexchange.com/questions/49569/  Unique lines based on the first field   sort, CLI
6:http://unix.stackexchange.com/questions/49569/  Unique lines based on the first field

Para imprimir apenas o número da linha, você pode fazer

awk '{print $1}' file | sort | uniq -c | 
 while read num dupe; do [[ $num > 1 ]] && grep -n -- "$dupe" file; done | cut -d: -f 1

E para imprimir apenas a linha:

awk '{print $1}' file | sort | uniq -c | 
while read num dupe; do [[ $num > 1 ]] && grep -n -- "$dupe" file; done | cut -d: -f 2-

Explicação:

O awkscript apenas imprime o primeiro campo separado do espaço do arquivo. Use $Npara imprimir o enésimo campo. sortclassifica e uniq -cconta as ocorrências de cada linha.

Isso é passado para o whileloop, que salva o número de ocorrências como $nume a linha como $dupee se $numfor maior que uma (por isso é duplicada pelo menos uma vez); ela pesquisará o arquivo para essa linha, usando -npara imprimir o número da linha. O --diz grepque o que se segue não é uma opção de linha de comando, útil para quando $dupecomeçar -.


1

Sem dúvida, o mais detalhado da lista provavelmente poderia ser mais curto:

#!/usr/bin/python3
import collections
file = "file.txt"

def find_duplicates(file):
    with open(file, "r") as sourcefile:
        data = sourcefile.readlines()
    splitlines = [
        (index, data[index].split("  ")) for index in range(0, len(data))
        ]
    lineheaders = [item[1][0] for item in splitlines]
    dups = [x for x, y in collections.Counter(lineheaders).items() if y > 1]
    dupsdata = []
    for item in dups:
        occurrences = [
            splitlines_item[0] for splitlines_item in splitlines\
                       if splitlines_item[1][0] == item
            ]
        corresponding_lines = [
            "["+str(index)+"] "+data[index] for index in occurrences
            ]
        dupsdata.append((occurrences, corresponding_lines))

    # printing output   
    print("found duplicates:\n"+"-"*17)
    for index in range(0, len(dups)):
        print(dups[index], dupsdata[index][0])
        lines = [item for item in dupsdata[index][1]]
        for line in lines:
            print(line, end = "")


find_duplicates(file)

fornece um arquivo de texto como:

monkey  banana
dog  bone
monkey  banana peanut
cat  mice
dog  cowmeat

uma saída como:

found duplicates:
-----------------
dog [1, 4]
[1] dog  bone
[4] dog  cowmeat
monkey [0, 2]
[0] monkey  banana
[2] monkey  banana peanut

Depois de escolher as linhas para remover:

removelist = [2,1]

def remove_duplicates(file, removelist):
    removelist = sorted(removelist, reverse=True)
    with open(file, "r") as sourcefile:
        data = sourcefile.readlines()
    for index in removelist:
        data.pop(index)
    with open(file, "wt") as sourcefile:
        for line in data:
            sourcefile.write(line)

remove_duplicates(file, removelist)

0

Veja o seguinte classificado file.txt:

addons.mozilla.org/en-US/firefox/addon/click-to-play-per-element/ ::: C2P per-element
addons.mozilla.org/en-us/firefox/addon/prospector-oneLiner/ ::: OneLiner
askubuntu.com/q/21033 ::: What is the difference between gksudo and gksu?
askubuntu.com/q/21148 ::: openoffice calc sheet tabs (also askubuntu.com/q/138623)
askubuntu.com/q/50540 ::: What is Ubuntu's Definition of a "Registered Application"?
askubuntu.com/q/53762 ::: How to use lm-sensors?
askubuntu.com/q/53762 ::: how-to-use-to-use-lm-sensors
stackoverflow.com/q/4594319 ::: bash - shell replace cr\lf by comma
stackoverflow.com/q/4594319 ::: shell replace cr\lf by comma
wiki.ubuntu.com/ClipboardPersistence ::: ClipboardPersistence
wiki.ubuntu.com/ClipboardPersistence ::: ClipboardPersistence - Ubuntu Wiki
www.youtube.com/watch?v=1olY5Qzmbk8 ::: Create new mime types in Ubuntu
www.youtube.com/watch?v=2hu9JrdSXB8 ::: Change mouse cursor
www.youtube.com/watch?v=Yxfa2fXJ1Wc ::: Mouse cursor size

Como a lista é curta, posso ver (depois da classificação) que existem três conjuntos de duplicatas.

Então, por exemplo, posso optar por manter:

askubuntu.com/q/53762 ::: How to use lm-sensors?

ao invés de

askubuntu.com/q/53762 ::: how-to-use-to-use-lm-sensors

Mas para uma lista mais longa, isso será difícil. Com base nas duas respostas, uma sugerindo uniqe a outra sugerindo cut, acho que esse comando me fornece a saída que eu gostaria:

$ cut -d " " -f1 file.txt | uniq -d
askubuntu.com/q/53762
stackoverflow.com/q/4594319
wiki.ubuntu.com/ClipboardPersistence
$

Atualizei minha resposta com outra variante de cut. Se você estiver executando um trabalho de desduplicação, os números de linha podem ser muito úteis. Para imprimir todas as duplicatas, use a -Dopção em vez de -d.
Lekensteyn

Eu acho melhor você usar: for dup in $(cut -d " " -f1 file.txt | uniq -d); do grep -n $dup file.txt; donecomo na minha resposta. Ele fornecerá uma visualização melhor sobre o que você está interessado.
Radu Rădeanu 15/03

0

É assim que eu resolvi:

file_with_duplicates:

1,a,c
2,a,d
3,a,e <--duplicate
4,a,t
5,b,k <--duplicate
6,b,l
7,b,s
8,b,j
1,b,l
3,a,d <--duplicate
5,b,l <--duplicate

Arquivo classificado e deduplicado pelas colunas 1 e 2:

sort -t',' -k1,1 -k2,2 -u file_with_duplicates

Arquivo classificado apenas pelas colunas 1 e 2:

sort -t',' -k1,1 -k2,2 file_with_duplicates

Mostre apenas a diferença:

diff <(sort -t',' -k1,1 -k2,2 -u file_with_duplicates) <(sort -t',' -k1,1 -k2,2 file_with_duplicates)

 3a4
   3,a,d
 6a8
   5,b,l
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.