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?

DK Bose
fonte
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.

Radu Rădeanu
fonte
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];
            }
        }
    }
}
Lekensteyn
fonte
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 -.

Terdon
fonte
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)
Jacob Vlijm
fonte
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
$
DK Bose
fonte
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
Clint Smith
fonte