bash, Linux: définir la différence entre deux fichiers texte

J'ai deux fichiers A-nodes_to_delete et B-nodes_to_keep. Chaque fichier a plusieurs lignes avec des identifiants numériques.

Je veux avoir la liste des id numériques qui sont dans nodes_to_delete, mais PAS dans nodes_to_keep, par exemple alt text http://mathworld.wolfram.com/images/equations/SetDifference/Inline1.gif.

Le faire dans une base de données PostgreSQL est excessivement lent. Une façon soignée de le faire dans bash en utilisant les outils CLI Linux?

UPDATE: cela semblerait être un travail pythonique, mais les fichiers sont vraiment, vraiment grand. J'ai résolu quelques problèmes similaires en utilisant uniq, sort et quelques techniques de théorie des ensembles. C'était environ deux ou trois ordres de grandeur plus rapide que les équivalents de la base de données.

54
demandé sur Tshepang 2010-03-24 19:39:00

5 réponses

La commande comm fait cela.

86
répondu msw 2018-07-13 16:17:06

Quelqu'un m'a montré comment faire exactement cela dans sh il y a quelques mois, et je n'ai pas pu le trouver pendant un moment... et en regardant, je suis tombé sur votre question. Ici, il est :

set_union () {
   sort $1 $2 | uniq
}

set_difference () {
   sort $1 $2 $2 | uniq -u
}

set_symmetric_difference() {
   sort $1 $2 | uniq -u
}
29
répondu slinkp 2016-08-10 22:35:22

Utilisez comm - il comparera deux fichiers triés ligne par ligne

La réponse à la question D'OP en utilisant cet exemple de configuration apparaît ci-dessous. Cette commande renverra des lignes uniques à deleteNodes, pas dans keepNodes

comm -1 -3 <(sort keepNodes) <(sort deleteNodes)

Explication: afficher les lignes uniques aux deleteNodes, masquer les autres lignes


Exemple de configuration

Nous allons utiliser keepNodes et deleteNodes. Ils sont utilisés comme entrée non triée.

$ cat > keepNodes <(echo bob; echo amber;)
$ cat > deleteNodes <(echo bob; echo ann;)

Par défaut sans arguments, comm imprime 3 colonnes

unique_to_FILE1
    unique_to_FILE2
        lines_appear_in_both

Ceci est un exemple barebones de comm sans arguments. Notez les trois colonnes.

$ comm <(sort keepNodes) <(sort deleteNodes)
amber
    ann
        bob

Suppression de la sortie de colonne

Supprimer la colonne 1, 2 ou 3 avec -N; notez que lorsqu'une colonne est masquée, les espaces se rétrécissent.

$ comm -1 <(sort keepNodes) <(sort deleteNodes)
ann
    bob
$ comm -2 <(sort keepNodes) <(sort deleteNodes)
amber
    bob
$ comm -3 <(sort keepNodes) <(sort deleteNodes)
amber
    ann
$ comm -1 -3 <(sort keepNodes) <(sort deleteNodes)
ann
$ comm -2 -3 <(sort keepNodes) <(sort deleteNodes)
amber
$ comm -1 -2 <(sort keepNodes) <(sort deleteNodes)
bob

Il échouera gracieusement lorsque vous oublierez de trier

comm: file 1 is not in sorted order

3
répondu activedecay 2017-02-12 23:23:24

comm a été spécifiquement conçu pour ce genre de cas d'utilisation, mais il nécessite une entrée triée.

awk est sans doute un meilleur outil pour cela car il est assez simple de trouver la différence de jeu, ne nécessite pas sort, et offre une flexibilité supplémentaire.

awk 'NR == FNR { a[$0]; next } !($0 in a)' nodes_to_keep nodes_to_delete

Peut-être, par exemple, que vous souhaitez seulement trouver la différence dans les lignes qui représentent des nombres non négatifs:

awk -v r='^[0-9]+$' 'NR == FNR && $0 ~ r {
    a[$0]
    next
} $0 ~ r && !($0 in a)' nodes_to_keep nodes_to_delete
2
répondu John B 2017-02-18 17:37:22

Peut-être que vous avez besoin d'une meilleure façon de le faire dans postgres, je peux à peu près parier que vous ne trouverez pas un moyen plus rapide de le faire en utilisant des fichiers plats. Vous devriez être capable de faire une simple jointure interne et en supposant que les deux cols id sont indexés, ce qui devrait être très rapide.

1
répondu Dark Castle 2010-03-24 16:50:15