Suppression de lignes d'un fichier qui se trouvent dans un autre fichier


126

J'ai un dossier f1:

line1
line2
line3
line4
..
..

Je veux supprimer toutes les lignes qui sont dans un autre fichier f2:

line2
line8
..
..

J'ai essayé quelque chose avec catet sed, qui n'était même pas proche de ce que je voulais. Comment puis-je faire ceci?



Si vous cherchez à supprimer des lignes d'un fichier qui "contiennent même" des chaînes d'un autre fichier (par exemple des correspondances partielles), voir unix.stackexchange.com/questions/145079
...

Réponses:


154

grep -v -x -f f2 f1 devrait faire l'affaire.

Explication:

  • -v pour sélectionner des lignes non correspondantes
  • -x pour correspondre uniquement aux lignes entières
  • -f f2 pour obtenir des modèles de f2

On peut à la place utiliser grep -Fou fgrepfaire correspondre des chaînes fixes à partir de f2plutôt que des modèles (au cas où vous voudriez supprimer les lignes d'une manière "ce que vous voyez si ce que vous obtenez" plutôt que de traiter les lignes f2comme des modèles regex).


22
Cela a une complexité O (n²) et commencera à prendre des heures une fois que les fichiers contiendront plus de quelques K lignes.
Arnaud Le Blanc

11
Déterminer quels algorithmes SO suggérés ont une complexité O (n ^ 2) n'a qu'une complexité O (n), mais peut encore prendre des heures à se concurrencer.
HDave

2
Je viens de l'essayer sur 2 fichiers d'environ 2k lignes chacun, et il a été tué par le système d'exploitation (d'accord, c'est une VM pas si puissante, mais quand même).
Trebor Rude

1
J'adore l'élégance de ceci; Je préfère la rapidité de la réponse de Jona Christopher Sahnwal.
Alex Hall

1
@ arnaud576875: Êtes-vous sûr? Cela dépend de la mise en œuvre de grep. S'il effectue un prétraitement f2correct avant de commencer la recherche, la recherche ne prendra que O (n) temps.
HelloGoodbye

57

Essayez plutôt comm (en supposant que f1 et f2 sont «déjà triés»)

comm -2 -3 f1 f2

5
Je ne suis pas sûr que commla solution soit la question n'indique pas que les lignes f1sont triées, ce qui est une condition préalable à l'utilisationcomm
gabuzo

1
Cela a fonctionné pour moi, car mes fichiers ont été triés et contenaient plus de 250 000 lignes dans l'un d'eux, seulement 28 000 dans l'autre. Merci!
Hiver

1
Lorsque cela fonctionne (les fichiers d'entrée sont triés), c'est extrêmement rapide!
Mike Jarvis le

Comme dans la solution d'arnaud576875, pour moi en utilisant cygwin, cela a éliminé les lignes en double dans le deuxième fichier qui pourraient vouloir être conservées.
Alex Hall

9
Vous pouvez utiliser la substitution de processus pour trier les fichiers d'abord, bien sûr:comm -2 -3 <(sort f1) <(sort f2)
davemyron

14

Pour exclure les fichiers 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 tolower()fonction le rend insensible à la casse, si vous en avez besoin.

La complexité algorithmique sera probablement O (n) (taille exclude-these.txt) + O (n) (taille from-this.txt)


Pourquoi dites-vous des fichiers qui ne sont pas trop volumineux? La peur ici est (je suppose) que awk exécute le système hors de la mémoire système pour créer le hachage, ou y a-t-il une autre limitation?
rogerdpack

pour les adeptes, il existe encore une autre option plus agressive pour «nettoyer» les lignes (puisque la comparaison doit être exacte pour utiliser le tableau associatif), ex unix.stackexchange.com/a/145132/8337
rogerdpack

@rogerdpack: Un fichier d'exclusion volumineux nécessitera un grand tableau de hachage (et un temps de traitement long). Un grand "from-this.txt" ne nécessitera qu'un long temps de traitement.
Suspendu jusqu'à nouvel ordre.

1
Cela échoue (c'est-à-dire ne produit aucune sortie) si exclude-these.txtest vide. La réponse de @ jona-christopher-sahnwaldt ci-dessous fonctionne dans ce cas. Vous pouvez également spécifier plusieurs fichiers, par exempleawk '{if (f==1) { r[$0] } else if (! ($0 in r)) { print $0 } } ' f=1 done.out failed.out f=2 all-files.out
Graham Russell

11

Similaire à la réponse de Dennis Williamson (principalement des changements syntaxiques, par exemple en définissant explicitement le numéro de fichier au lieu de l' NR == FNRastuce):

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, pas besoin de définir une valeur.

En supposant que awk utilise une table de hachage avec une recherche constante et un temps de mise à jour constant (en moyenne), la complexité temporelle de celle-ci sera O (n + m), où n et m sont les longueurs des fichiers. Dans mon cas, n était ~ 25 millions et m ~ 14000. La solution awk était beaucoup plus rapide que le tri, et j'ai également préféré conserver l'ordre d'origine.


En quoi cela diffère-t-il de la réponse de Dennis Williamson? La seule différence est-elle qu'il n'effectue pas d'affectation dans le hachage, donc légèrement plus rapide que cela? La complexité algorithmique est la même que la sienne?
rogerdpack

La différence est principalement syntaxique. Je trouve la variable fplus claire que NR == FNR, mais c'est une question de goût. L'affectation dans le hachage doit être si rapide qu'il n'y a pas de différence de vitesse mesurable entre les deux versions. Je pense que je me suis trompé sur la complexité - si la recherche est constante, la mise à jour doit également être constante (en moyenne). Je ne sais pas pourquoi je pensais que la mise à jour serait logarithmique. Je modifierai ma réponse.
jcsahnwaldt Réintègre Monica

J'ai essayé un tas de ces réponses, et celle-ci a été AMAZEBALLS rapide. J'avais des fichiers avec des centaines de milliers de lignes. A travaillé comme un charme!
M. T

1
C'est ma solution préférée. Il fonctionne avec plusieurs fichiers et également des fichiers d'exclusion vides, par exemple awk '{if (f==1) { r[$0] } else if (! ($0 in r)) { print $0 } } ' f=1 empty.file done.out failed.out f=2 all-files.out. Alors que l'autre awksolution échoue avec un fichier d'exclusion vide et ne peut en prendre qu'un.
Graham Russell

5

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 une complexité O (N ^ 2). Si vous voulez vous soucier des performances, 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, de même que la complexité O (n) (taille de a) + O (n) (taille de b)

voici une petite référence, gracieuseté de user576875, mais avec 100K 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 aucune différence entre les 2 fichiers générés.


1
Cela a une complexité O (n²) et commencera à prendre des heures une fois que les fichiers contiendront plus de quelques K lignes.
Arnaud Le Blanc

Je m'en fiche à ce stade, car il n'a pas mentionné de gros fichiers.
kurumi

3
Il n'est pas nécessaire d'être aussi défensif, ce n'est pas comme si @ user576875 avait voté contre votre réponse ou quoi que ce soit. :-)
John Parker

très belle seconde version, ruby ​​gagne :)
Arnaud Le Blanc

4

Quelques comparaisons temporelles 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 stdin et ici des chaînes:

echo $'a\nb' | comm -23 <(sort) <(sort <<< $'c\nb') # a

2

Semble être un travail adapté au 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

1

Avez-vous essayé cela 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

0

Pas une réponse de «programmation» mais voici une solution rapide et sale: il suffit d'aller sur http://www.listdiff.com/compare-2-lists-difference-tool .

Évidemment, cela ne fonctionnera pas pour des fichiers volumineux, mais cela a fait l'affaire pour moi. Quelques notes:

  • Je ne suis en aucun cas affilié au site Web (si vous ne me croyez toujours pas, vous pouvez simplement rechercher un autre outil en ligne; j'ai utilisé le terme de recherche "définir la liste des différences en ligne")
  • Le site Web lié semble passer des appels réseau sur chaque comparaison de liste, alors ne lui fournissez pas de données sensibles
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.