Si j'ai deux fichiers (avec des colonnes simples), une personne comme (fichier1)
34
67
89
92
102
180
blue2
3454
Et le deuxième fichier (fichier2)
23
56
67
69
102
200
Comment trouver des éléments communs dans les deux fichiers (intersection)? La production attendue dans cet exemple est
67
102
Notez que le nombre d'éléments (lignes) dans chaque fichier diffère. Les chiffres et les chaînes peuvent être mélangés. Ils peuvent ne pas être forcément triés. Chaque élément apparaît seulement une fois.
Chèque de temps Basé sur certaines des réponses ci-dessous.
# generate some data
>shuf -n2000000 -i1-2352452 > file1
>shuf -n2000000 -i1-2352452 > file2
#@ilkkachu
>time (join <(sort "file1") <(sort "file2") > out1)
real 0m15.391s
user 0m14.896s
sys 0m0.205s
>head out1
1
10
100
1000
1000001
#@Hauke
>time (grep -Fxf "file1" "file2" > out2)
real 0m7.652s
user 0m7.131s
sys 0m0.316s
>head out2
1047867
872652
1370463
189072
1807745
#@Roman
>time (comm -12 <(sort "file1") <(sort "file2") > out3)
real 0m13.533s
user 0m13.140s
sys 0m0.195s
>head out3
1
10
100
1000
1000001
#@ilkkachu
>time (awk 'NR==FNR { lines[$0]=1; next } $0 in lines' "file1" "file2" > out4)
real 0m4.587s
user 0m4.262s
sys 0m0.195s
>head out4
1047867
872652
1370463
189072
1807745
#@Cyrus
>time (sort file1 file2 | uniq -d > out8)
real 0m16.106s
user 0m15.629s
sys 0m0.225s
>head out8
1
10
100
1000
1000001
#@Sundeep
>time (awk 'BEGIN{while( (getline k < "file1")>0 ){a[k]}} $0 in a' file2 > out5)
real 0m4.213s
user 0m3.936s
sys 0m0.179s
>head out5
1047867
872652
1370463
189072
1807745
#@Sundeep
>time (Perl -ne 'BEGIN{ $h{$_}=1 while <STDIN> } print if $h{$_}' <file1 file2 > out6)
real 0m3.467s
user 0m3.180s
sys 0m0.175s
>head out6
1047867
872652
1370463
189072
1807745
La version Perl était la plus rapide suivie de AWK. Tous les fichiers de sortie ont eu le même nombre de lignes.
Pour des raisons de comparaison, j'ai trié la sortie numériquement de manière à ce que la sortie soit identique.
#@ilkkachu
>time (join <(sort "file1") <(sort "file2") | sort -k1n > out1)
real 0m17.953s
user 0m5.306s
sys 0m0.138s
#@Hauke
>time (grep -Fxf "file1" "file2" | sort -k1n > out2)
real 0m12.477s
user 0m11.725s
sys 0m0.419s
#@Roman
>time (comm -12 <(sort "file1") <(sort "file2") | sort -k1n > out3)
real 0m16.273s
user 0m3.572s
sys 0m0.102s
#@ilkkachu
>time (awk 'NR==FNR { lines[$0]=1; next } $0 in lines' "file1" "file2" | sort -k1n > out4)
real 0m8.732s
user 0m8.320s
sys 0m0.261s
#@Cyrus
>time (sort file1 file2 | uniq -d > out8)
real 0m19.382s
user 0m18.726s
sys 0m0.295s
#@Sundeep
>time (awk 'BEGIN{while( (getline k < "file1")>0 ){a[k]}} $0 in a' file2 | sort -k1n > out5)
real 0m8.758s
user 0m8.315s
sys 0m0.255s
#@Sundeep
>time (Perl -ne 'BEGIN{ $h{$_}=1 while <STDIN> } print if $h{$_}' <file1 file2 | sort -k1n > out6)
real 0m7.732s
user 0m7.300s
sys 0m0.310s
>head out1
1
2
3
4
5
Toutes les sorties sont maintenant identiques.
Dans awk
, ceci charge le premier fichier entièrement en mémoire:
$ awk 'NR==FNR { lines[$0]=1; next } $0 in lines' file1 file2
67
102
Ou, si vous voulez garder une trace de combien de fois une ligne donnée apparaît:
$ awk 'NR==FNR { lines[$0] += 1; next } lines[$0] {print; lines[$0] -= 1}' file1 file2
join
pourrait faire cela, bien qu'il exige que les fichiers d'entrée soient triés, vous devez donc le faire en premier et le faire perd la commande originale:
$ join <(sort file1) <(sort file2)
102
67
Simple comm
+ sort
Solution:
comm -12 <(sort file1) <(sort file2)
-12
- Supprimer la colonne 1
et 2
(lignes propres à FILE1
et FILE2
respectivement), émettant ainsi uniquement des lignes communes (qui apparaissent dans les deux fichiers)awk
awk 'NR==FNR { p[NR]=$0; next; }
{ for(val in p) if($0==p[val]) { delete p[val]; print; } }' file1 file2
C'est la bonne solution car (pour les fichiers volumineux), il devrait être le plus rapide car il omet à la fois imprimer la même entrée plus d'une fois et vérifier une entrée après sa correspondance.
grep
grep -Fxf file1 file2
Cela émettrait la même entrée à plusieurs reprises s'il se produit plus d'une fois dans file2
.
trier
Pour le plaisir (devrait être beaucoup plus lent que grep
):
sort -u file1 >t1
sort -u file2 >t2
sort t1 t2 | uniq -d
Avec GNU UNIQ:
sort file1 file2 | uniq -d
Sortir:
102 [.____] 67 [.____]
légèrement différent awk
version et équivalent Perl
version
temps rapporté pour trois courses consécutives
$ # just realized shuf -n2000000 -i1-2352452 can be used too ;)
$ shuf -i1-2352452 | head -n2000000 > f1
$ shuf -i1-2352452 | head -n2000000 > f2
$ time awk 'NR==FNR{a[$1]; next} $0 in a' f1 f2 > t1
real 0m3.322s
real 0m3.094s
real 0m3.029s
$ time awk 'BEGIN{while( (getline k < "f1")>0 ){a[k]}} $0 in a' f2 > t2
real 0m2.731s
real 0m2.777s
real 0m2.801s
$ time Perl -ne 'BEGIN{ $h{$_}=1 while <STDIN> } print if $h{$_}' <f1 f2 > t3
real 0m2.643s
real 0m2.690s
real 0m2.630s
$ diff -s t1 t2
Files t1 and t2 are identical
$ diff -s t1 t3
Files t1 and t3 are identical
$ du -h f1 f2 t1
15M f1
15M f2
13M t1