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

8

Eu tenho minhas referências como um arquivo de texto com uma longa lista de entradas e cada uma tem 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.

Eu quero identificar, mas não remover, entradas que tenham o primeiro campo (URL de referência) idêntico. Eu sei sobre sort -k1,1 -u , mas isso irá automaticamente (não interativamente) remover todos, exceto o primeiro hit. Existe uma maneira de me avisar para que eu possa escolher qual deles manter?

No trecho 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 (sort, CLI) e deletar 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, posso limpar manualmente, excluindo pessoalmente as linhas # 1 e # 3?

    
por DK Bose 15.03.2014 / 11:28

6 respostas

7

Se eu 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.txt é 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.

    
por Radu Rădeanu 15.03.2014 / 12:30
6

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

Como a encomenda de linhas duplicadas não é importante para você, você deve classificá-la primeiro. Em seguida, use uniq para 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 opção -d . Veja a página de manual de uniq para detalhes.

Se você realmente não se importa com as partes após o primeiro campo, você pode usar o seguinte comando para encontrar chaves duplicadas e imprimir cada número de linha para ele (anexe outro | sort -n para ter a saída ordenada 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 torna a automação difícil é 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 recebe seu arquivo de texto como entrada e imprime todas as linhas duplicadas, para que você possa decidir quais 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];
            }
        }
    }
}
    
por Lekensteyn 15.03.2014 / 12:13
2

Se eu ler 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

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ê poderia 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 script awk apenas imprime o primeiro campo separado por espaço do arquivo. Use $N para imprimir o campo Nth. sort classifica e uniq -c conta as ocorrências de cada linha.

Isso é passado ao loop while , que salva o número de ocorrências como $num e a linha como $dupe e, se $num for maior que um (por isso, é duplicado pelo menos uma vez). o arquivo para essa linha, usando -n para imprimir o número da linha. O -- informa grep que o que segue não é uma opção de linha de comando, útil para quando $dupe pode começar com - .

    
por terdon 15.03.2014 / 19:14
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)

dá em 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 a 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)
    
por Jacob Vlijm 16.03.2014 / 16:05
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 (após a classificação) que há três conjuntos de duplicatas.

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

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

em vez 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 uniq e a outra sugerindo cut , acho que esse comando me dá a saída que gostaria:

$ cut -d " " -f1 file.txt | uniq -d
askubuntu.com/q/53762
stackoverflow.com/q/4594319
wiki.ubuntu.com/ClipboardPersistence
$
    
por DK Bose 15.03.2014 / 13:16
0

Foi assim que 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 deduzido 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

Mostrar 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
    
por Clint Smith 16.04.2016 / 01:17