J'ai un fichier f1
:
line1
line2
line3
line4
..
..
Je veux supprimer toutes les lignes qui se trouvent dans un autre fichier f2
:
line2
line8
..
..
J'ai essayé quelque chose avec cat
et sed
, ce qui n'était même pas proche de ce que j'avais l'intention de faire. Comment puis-je faire ceci?
grep -v -x -f f2 f1
devrait faire l'affaire.
Explication:
-v
pour sélectionner des lignes non concordantes-x
pour ne faire correspondre que des lignes entières-f f2
pour obtenir des motifs de f2
On peut plutôt utiliser grep -F
ou fgrep
pour correspondre à chaînes fixes de f2
plutôt que motifs (au cas où vous voudriez supprimer les lignes de la manière "ce que vous voyez si ce que vous obtenez" plutôt que de traiter les lignes dans f2
comme motifs de regex).
Essayez la comm à la place (en supposant que f1 et f2 sont "déjà triés")
comm -2 -3 f1 f2
Pour les fichiers d'exclusion qui ne sont pas trop volumineux, vous pouvez utiliser les tableaux associatifs d'AWK.
awk 'NR == FNR { list[tolower($0)]=1; next } { if (! list[tolower($0)]) print }' exclude-these.txt from-this.txt
La sortie sera dans le même ordre que le fichier "from-this.txt". La fonction tolower()
le rend insensible à la casse, si vous en avez besoin.
La complexité algorithmique sera probablement O(n) (taille de exclude-ceux.txt)) + O(n) (taille de this this.txt))
Semblable à la réponse de Dennis Williamson (principalement des modifications syntaxiques, par exemple définir explicitement le numéro de fichier au lieu du tour NR == FNR
):
awk '{if (f==1) { r[$0] } else if (! ($0 in r)) { print $0 } } ' f=1 exclude-these.txt f=2 from-this.txt
L'accès à r[$0]
Crée l'entrée pour cette ligne, il n'est pas nécessaire de définir une valeur.
En supposant qu'awk utilise une table de hachage avec une recherche constante et un temps de mise à jour constant (en moyenne), la complexité temporelle de cette opération sera de O (n + m), où n et m sont les longueurs des fichiers. Dans mon cas, n était environ 25 millions et environ 14 000. La solution awk était beaucoup plus rapide que la sorte, et j'ai également préféré conserver l'ordre d'origine.
si vous avez Ruby (1.9+)
#!/usr/bin/env Ruby
b=File.read("file2").split
open("file1").each do |x|
x.chomp!
puts x if !b.include?(x)
end
Qui a la complexité O (N ^ 2). Si vous voulez vous soucier de la performance, voici une autre version
b=File.read("file2").split
a=File.read("file1").split
(a-b).each {|x| puts x}
qui utilise un hachage pour effectuer la soustraction, la complexité est donc égale à O(n) (taille de a) + O(n) (taille de b)
voici un petit repère, avec la permission de user576875, mais avec 100 000 lignes, de ce qui précède:
$ for i in $(seq 1 100000); do echo "$i"; done|sort --random-sort > file1
$ for i in $(seq 1 2 100000); do echo "$i"; done|sort --random-sort > file2
$ time Ruby test.rb > Ruby.test
real 0m0.639s
user 0m0.554s
sys 0m0.021s
$time sort file1 file2|uniq -u > sort.test
real 0m2.311s
user 0m1.959s
sys 0m0.040s
$ diff <(sort -n Ruby.test) <(sort -n sort.test)
$
diff
a été utilisé pour montrer qu'il n'y a pas de différence entre les 2 fichiers générés.
Quelques comparaisons de temps entre diverses autres réponses:
$ for n in {1..10000}; do echo $RANDOM; done > f1
$ for n in {1..10000}; do echo $RANDOM; done > f2
$ time comm -23 <(sort f1) <(sort f2) > /dev/null
real 0m0.019s
user 0m0.023s
sys 0m0.012s
$ time Ruby -e 'puts File.readlines("f1") - File.readlines("f2")' > /dev/null
real 0m0.026s
user 0m0.018s
sys 0m0.007s
$ time grep -xvf f2 f1 > /dev/null
real 0m43.197s
user 0m43.155s
sys 0m0.040s
sort f1 f2 | uniq -u
_ n'est même pas une différence symétrique, car elle supprime les lignes qui apparaissent plusieurs fois dans l'un ou l'autre fichier.
comm peut également être utilisé avec les chaînes stdin et here:
echo $'a\nb' | comm -23 <(sort) <(sort <<< $'c\nb') # a
Semble être un travail approprié pour le shell SQLite:
create table file1(line text);
create index if1 on file1(line ASC);
create table file2(line text);
create index if2 on file2(line ASC);
-- comment: if you have | in your files then specify “ .separator ××any_improbable_string×× ”
.import 'file1.txt' file1
.import 'file2.txt' file2
.output result.txt
select * from file2 where line not in (select line from file1);
.q
Avez-vous essayé this avec sed?
sed 's#^#sed -i '"'"'s%#g' f2 > f2.sh
sed -i 's#$#%%g'"'"' f1#g' f2.sh
sed -i '1i#!/bin/bash' f2.sh
sh f2.sh