bash, Linux: différence entre deux fichiers Définir texte
-
22-09-2019 - |
Question
J'ai deux fichiers A
-nodes_to_delete
et B
-nodes_to_keep
. Chaque fichier a un grand nombre de lignes avec Ids numériques.
Je veux avoir la liste des identifiants numériques qui sont nodes_to_delete
mais pas dans nodes_to_keep
, par exemple
.
Le faire au sein d'une base de données PostgreSQL est excessivement lent. Toute belle façon de le faire en utilisant des outils bash Linux CLI?
Mise à jour: Cela semble être un travail Pythonic, mais les fichiers sont vraiment, vraiment grand. J'ai résolu des problèmes similaires en utilisant uniq
, sort
et certaines techniques de théorie des ensembles. Il était environ deux ou trois ordres de grandeur plus rapide que les équivalents de base de données.
La solution
La commande comm fait cela.
Autres conseils
Quelqu'un m'a montré comment faire exactement cela en sh il y a deux mois, et je ne pouvais pas trouver pendant un certain temps ... 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
}
Utilisez comm
- il compare deux fichiers triés par ligne
La réponse courte à votre question
Cette commande renvoie des lignes uniques à deleteNodes, mais pas dans les lignes keepNodes.
comm -1 -3 <(sort keepNodes) <(sort deleteNodes)
Exemple de configuration
Créons les fichiers nommés keepNodes
et deleteNodes
, et les utiliser comme entrée pour la commande non triés de comm
.
$ cat > keepNodes <(echo bob; echo amber;)
$ cat > deleteNodes <(echo bob; echo ann;)
Par défaut, en cours d'exécution sans comm arguments imprime 3 colonnes avec cette mise en page:
lines_unique_to_FILE1
lines_unique_to_FILE2
lines_which_appear_in_both
En utilisant notre exemple fichiers ci-dessus, exécutez comm sans arguments. Notez les trois colonnes.
$ comm <(sort keepNodes) <(sort deleteNodes)
amber
ann
bob
La suppression de sortie de colonne
Supprimer la colonne 1, 2 ou 3 avec -N; noter que lorsqu'une colonne est cachée, les espaces blancs se rétrécit vers le haut.
$ 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
Le tri est important!
Si vous exécutez comm sans d'abord trier le fichier, il échoue avec élégance avec un message sur lequel le fichier ne sont pas triées.
comm: file 1 is not in sorted order
comm
a été spécialement 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 ensemble, 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, vous souhaitez trouver que 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
Peut-être 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 en mesure de faire une jointure interne simple et en supposant que les deux id sont Col. indexés qui devrait être très rapide.
Alors, ce qui est légèrement différent des autres réponses. Je ne peux pas dire qu'un compilateur C ++ est exactement un « outil Linux CLI », mais en cours d'exécution g++ -O3 -march=native -o set_diff main.cpp
(avec le code ci-dessous main.cpp
peut faire l'affaire):
#include<algorithm>
#include<iostream>
#include<iterator>
#include<fstream>
#include<string>
#include<unordered_set>
using namespace std;
int main(int argc, char** argv) {
ifstream keep_file(argv[1]), del_file(argv[2]);
unordered_multiset<string> init_lines{istream_iterator<string>(keep_file), istream_iterator<string>()};
string line;
while (getline(del_file, line)) {
init_lines.erase(line);
}
copy(init_lines.begin(),init_lines.end(), ostream_iterator<string>(cout, "\n"));
}
Pour utiliser, il suffit de lancer set_diff B A
( pas A B
, depuis B
est nodes_to_keep
) et la différence résultant sera imprimé à stdout.
Notez que j'ai renoncé à quelques meilleures pratiques C de conserver le code plus simple.
De nombreuses optimisations de vitesse supplémentaires pourraient être (au prix de plus de mémoire). mmap
serait également particulièrement utile pour les grands ensembles de données, mais qui ferait le code beaucoup plus impliqué.
Puisque vous avez mentionné que les ensembles de données sont grandes, je pensais que la lecture d'une ligne nodes_to_delete
à un moment peut-être une bonne idée de réduire la consommation de mémoire. L'approche adoptée dans le code ci-dessus ne sont pas particulièrement efficace s'il y a beaucoup de dupes dans votre nodes_to_delete
. En outre, l'ordre ne se conserve pas.
Quelque chose plus facile à copier et coller dans bash
(à savoir sauter création de main.cpp
):
g++ -O3 -march=native -xc++ -o set_diff - <<EOF
#include<algorithm>
#include<iostream>
#include<iterator>
#include<fstream>
#include<string>
#include<unordered_set>
using namespace std;
int main(int argc, char** argv) {
ifstream keep_file(argv[1]), del_file(argv[2]);
unordered_multiset<string> init_lines{istream_iterator<string>(keep_file), istream_iterator<string>()};
string line;
while (getline(del_file, line)) {
init_lines.erase(line);
}
copy(init_lines.begin(),init_lines.end(), ostream_iterator<string>(cout, "\n"));
}
EOF