Identifier les lignes en double dans un fichier sans les supprimer?


11

J'ai mes références sous forme de fichier texte avec une longue liste d'entrées et chacune a deux (ou plus) champs.

La première colonne est l'URL de la référence; la deuxième colonne est le titre qui peut varier un peu selon la façon dont la saisie a été effectuée. Idem pour le troisième champ qui peut ou non être présent.

Je veux identifier mais pas supprimer les entrées dont le premier champ (URL de référence) est identique. Je sais sort -k1,1 -umais cela supprimera automatiquement (de manière non interactive) tout sauf le premier hit. Existe-t-il un moyen de me le faire savoir afin que je puisse choisir lequel conserver?

Dans l'extrait ci-dessous de trois lignes qui ont le même premier champ ( http://unix.stackexchange.com/questions/49569/), je voudrais garder la ligne 2 car elle a des balises supplémentaires (tri, CLI) et supprimer les lignes # 1 et # 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-t-il un programme pour aider à identifier ces "doublons"? Ensuite, je peux nettoyer manuellement en supprimant personnellement les lignes # 1 et # 3?


Je ne comprends pas très bien votre exemple ... pourriez-vous donner une version plus simplifiée de l'entrée et de la sortie attendue?
Oli

Veuillez voir si c'est plus clair maintenant?
DK Bose

Réponses:


9

Si je comprends votre question, je pense que vous avez besoin de quelque chose comme:

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

file.txtest votre fichier contenant des données vous concernant.

Dans la sortie, vous verrez le nombre de lignes et de lignes où le premier champ est trouvé deux fois ou plus.


3
Merci: cut -d " " -f1 file.txt | uniq -dme donne même une belle sortie.
DK Bose

@DKBose Il y a probablement plus de possibilités, mais je voulais aussi utiliser votre commande.
Radu Rădeanu

Merci. La deuxième commande est celle que j'aime. Vous pouvez supprimer le premier. Et si vous expliquez le code, ce serait bien aussi :)
DK Bose

10

Il s'agit d'un problème classique qui peut être résolu avec la uniqcommande. uniqpeut détecter les lignes consécutives en double et supprimer les doublons ( -u, --unique) ou ne conserver que les doublons ( -d, --repeated).

Étant donné que la commande de lignes en double n'est pas importante pour vous, vous devez d'abord la trier. Utilisez ensuite uniqpour imprimer uniquement des lignes uniques:

sort yourfile.txt | uniq -u

Il existe également une option -c( --count) qui imprime le nombre de doublons pour l' -doption. Voir la page de manuel de uniqpour plus de détails.


Si vous ne vous souciez vraiment pas des pièces après le premier champ, vous pouvez utiliser la commande suivante pour rechercher des clés en double et imprimer chaque numéro de ligne (ajoutez-en une autre | sort -npour que la sortie soit triée par ligne):

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

Étant donné que vous souhaitez voir les lignes en double (en utilisant le premier champ comme clé), vous ne pouvez pas utiliser directement uniq. Le problème qui rend l'automatisation difficile est que les parties du titre varient, mais un programme ne peut pas déterminer automatiquement quel titre doit être considéré comme le dernier.

Voici un script AWK (enregistrez-le script.awk) qui prend votre fichier texte en entrée et imprime toutes les lignes en double afin que vous puissiez décider lequel supprimer. ( 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];
            }
        }
    }
}

Je pense que c'est proche de ce que je veux mais j'ai besoin de l'opposé de `-f, --skip-fields = N (éviter de comparer les N premiers champs). En d'autres termes, je souhaite que seul le premier champ, les URL, soit pris en compte.
DK Bose

@DKBose Il y a une option -w( --check-chars) pour limiter à un nombre fixe de caractères, mais vu votre exemple, vous avez des premiers champs variables. Étant donné uniqque ne prend pas en charge la sélection de champs, vous devez utiliser une solution de contournement. Je vais inclure un exemple AWK car c'est plus facile.
Lekensteyn

Oui, je regardais juste -wmais la longueur du premier champ est variable :(
DK Bose

@DKBose S'il vous plaît voir la dernière modification
Lekensteyn

1
Je reçois awk: script.awk: ligne 4: erreur de syntaxe à ou près [awk: script.awk: ligne 10: erreur de syntaxe à ou près [awk: script.awk: ligne 18: erreur de syntaxe à ou près}
DK Bose

2

Si je lis bien, tout ce dont vous avez besoin est quelque chose comme

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

Cela affichera le numéro de la ligne qui contient la dupe et la ligne elle-même. Par exemple, en utilisant ce fichier:

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

Il produira cette sortie:

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

Pour imprimer uniquement le numéro de la ligne, vous pouvez faire

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

Et pour imprimer uniquement la ligne:

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

Explication:

Le awkscript imprime simplement le 1er champ séparé par des espaces du fichier. Utilisez $Npour imprimer le champ Nième. sortle trie et uniq -ccompte les occurrences de chaque ligne.

Ceci est ensuite passé à la whileboucle qui enregistre le nombre d'occurrences as $numet la ligne as $dupeet if $numest supérieure à un (donc il est dupliqué au moins une fois), il recherchera le fichier pour cette ligne, en utilisant -npour imprimer le numéro de ligne. Le --indique grepque ce qui suit n'est pas une option de ligne de commande, utile pour quand $dupepeut commencer -.


1

Sans doute le plus verbeux de la liste, pourrait probablement être plus court:

#!/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)

donne sur un fichier texte comme:

monkey  banana
dog  bone
monkey  banana peanut
cat  mice
dog  cowmeat

une sortie comme:

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

Une fois que vous avez choisi les lignes à supprimer:

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

Voir les éléments suivants triés 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

Parce que la liste est courte, je peux voir (après tri) qu'il y a trois ensembles de doublons.

Ensuite, par exemple, je peux choisir de conserver:

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

plutôt que

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

Mais pour une liste plus longue, ce sera difficile. Sur la base des deux réponses, l'une suggérant uniqet l'autre suggérant cut, je trouve que cette commande me donne la sortie que j'aimerais:

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

J'ai mis à jour ma réponse avec une autre variante de cut. Si vous effectuez un travail de déduplication, les numéros de ligne peuvent être très utiles. Pour imprimer tous les doublons, utilisez l' -Doption au lieu de -d.
Lekensteyn

Je pense que vous feriez mieux de l'utiliser: for dup in $(cut -d " " -f1 file.txt | uniq -d); do grep -n $dup file.txt; donecomme dans ma réponse. Cela vous donnera un meilleur aperçu de ce qui vous intéresse.
Radu Rădeanu

0

Voici comment je l'ai résolu:

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

Fichier trié et dédupliqué par les colonnes 1 et 2:

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

Fichier trié uniquement par les colonnes 1 et 2:

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

Afficher uniquement la différence:

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
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.