web-dev-qa-db-fra.com

Supprimer des lignes d'un fichier qui sont dans un autre fichier

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?

114
lalli

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).

137
gabuzo

Essayez la comm à la place (en supposant que f1 et f2 sont "déjà triés")

comm -2 -3 f1 f2
48

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))

12
Dennis Williamson

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.

10
jcsahnwaldt

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.

5
kurumi

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
4
Lri

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
2
Benoit

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
1
Ruan