2010-12-06 52 views
96

J'ai un grand fichier A (composé de courriels), une ligne pour chaque courrier. J'ai aussi un autre fichier B qui contient un autre ensemble de mails.Comment faire pour supprimer les lignes qui apparaissent sur le fichier B d'un autre fichier A?

Quelle commande dois-je utiliser pour supprimer toutes les adresses qui apparaissent dans le fichier B à partir du fichier A.

Ainsi, si le fichier A contient:

A 
B 
C 

et le fichier B contenait:

B  
D 
E 

fichier, puis A doit être laissé avec:

A 
C 

Maintenant, je sais que c'est une question qui aurait pu être posée plus souvent, mais je n'ai trouvé que one command online qui m'a donné une erreur avec un mauvais délimiteur.

Toute aide serait grandement appréciée! Quelqu'un va sûrement arriver avec un one-liner intelligent, mais je ne suis pas l'expert en coquillages.

+0

possible copie de [Suppression de lignes d'un fichier qui se trouve dans un autre fichier] (http://stackoverflow.com/questions/4780203/deleting-lines-from-one-file-which-are-in-another-file) – tripleee

+0

@tripleee Rappelez-vous, le mien est un peu plus âgé et l'autre a eu des votes à fermer en tant que dupe de cette o ne – slhck

+1

La plupart du temps si les réponses sont pour des fichiers triés, et le plus évident est manquant, ce qui bien sûr n'est pas votre faute, mais cela rend l'autre plus utile. – tripleee

Répondre

136
comm -23 file1 file2 

-23 supprime les lignes qui sont dans les deux fichiers, ou seulement dans le fichier 2. Les fichiers doivent être triés (ils sont dans votre exemple), mais sinon, les tuyaux par sort premier ...

Voir la man page here

+3

'comm -23 fichier1 fichier2> fichier3' affichera le contenu dans fichier1 pas dans fichier2, dans fichier3. Et puis 'mv file3 file1' finirait par effacer le contenu redondant dans file1. – 8090PZ

16

Une autre façon de faire la même chose (nécessite également une entrée triée):

join -v 1 fileA fileB 

Dans Bash, si les fichiers ne sont pas pré-classés:

join -v 1 <(sort fileA) <(sort fileB) 
4

Vous pouvez le faire à moins que vos fichiers sont classés

diff file-a file-b --new-line-format="" --old-line-format="%L" --unchanged-line-format="" > file-a 

--new-line-format est pour les lignes qui sont dans le fichier b mais pas dans un --old-.. est-il pour les lignes qui sont dans le fichier un mais pas dans b --unchanged-.. est pour les lignes qui sont dans les deux. %L fait en sorte que la ligne est imprimée exactement.

man diff 

pour plus de détails

+1

Vous dites que cela fonctionnera à moins que les fichiers ne soient triés. Quels problèmes se produisent s'ils sont triés? Et s'ils sont partiellement triés? –

+1

C'était en réponse à la solution ci-dessus que l'utilisation suggérée de la commande 'comm'. 'comm 'nécessite que les fichiers soient triés, donc s'ils sont triés, vous pouvez également utiliser cette solution. Vous pouvez utiliser cette solution indépendamment du fait que le fichier soit trié ou non par – aec

33

grep -Fvxf <lines-to-remove> <all-lines>

  • travaille sur des fichiers non triés
  • maintient l'ordre
  • is POSIX

Exemple:

cat <<EOF > A 
b 
1 
a 
0 
01 
b 
1 
EOF 

cat <<EOF > B 
0 
1 
EOF 

grep -Fvxf B A 

Sortie:

b 
a 
01 
b 

Explication:

  • -F: utiliser des chaînes littérales au lieu de la valeur par défaut BRE
  • -x: considérer que les matchs qui correspondent à l'ensemble ligne
  • -v: imprimer non correspondant
  • -f file: prendre des modèles à partir du fichier donné

Cette méthode est plus lente sur les fichiers prétriés que d'autres méthodes, car il est plus général. Si la vitesse des questions aussi bien, voir: Fast way of finding lines in one file that are not in another?

Voir aussi: https://unix.stackexchange.com/questions/28158/is-there-a-tool-to-get-the-lines-in-one-file-that-are-not-in-another

25

awk à la rescousse!

Cette solution ne nécessite pas d'entrées triées. Vous devez d'abord fournir fileB.

awk 'NR==FNR{a[$0];next} !($0 in a)' fileB fileA 

retours

A 
C 

Comment ça marche?

idiome NR==FNR{a[$0];next} est pour stocker le premier fichier dans un tableau associatif comme clés pour un test plus tard « contient ».

NR==FNR Vérifie si nous analysons le premier fichier, où le compteur de lignes global (NR) est égal au compteur de ligne de fichier actuel (FNR).

a[$0] ajoute la ligne courante au tableau associatif clé, notez que ce se comporte comme un ensemble, où il n'y aura pas de valeurs en double (clés)

!($0 in a) nous sommes maintenant dans le fichier suivant (s), in est un test contient, ici il vérifie si la ligne actuelle est dans l'ensemble que nous avons peuplé dans la première étape du premier fichier, ! annule la condition. Ce qui manque ici, c'est l'action, qui par défaut est {print} et n'est généralement pas écrite explicitement.

Notez que cela peut maintenant être utilisé pour supprimer les mots en liste noire.

$ awk '...' badwords allwords > goodwords 

Avec un léger changement, il peut nettoyer plusieurs listes et créer des versions nettoyées.

$ awk 'NR==FNR{a[$0];next} !($0 in a){print > FILENAME".clean"}' bad file1 file2 file3 ... 
+0

. Pour l'utiliser sur la ligne de commande dans GnuWin32 dans Windows, remplacez les simples grignotements par des guillemets doubles. fonctionne un régal. Merci beaucoup. – twobob

+0

Cela fonctionne mais comment serai-je capable de rediriger la sortie vers le fichier A sous la forme de A (avec une nouvelle ligne) B –

+0

Je suppose que vous voulez dire A \ nC, écrire d'abord dans un fichier temporaire et écraser le fichier original ' ...> tmp && mv tmp fileA' – karakfa

3

Ce raffinement de la bonne réponse de @ karakfa peut être sensiblement plus rapide pour les très gros fichiers. Comme pour cette réponse, aucun fichier n'a besoin d'être trié, mais la vitesse est assurée grâce aux tableaux associatifs d'awk. Seul le fichier de recherche est conservé en mémoire.

Cette formulation permet également la possibilité d'utiliser un seul champ ($ N) dans le fichier d'entrée dans la comparaison.

# Print lines in the input unless the value in column $N 
# appears in a lookup file, $LOOKUP; 
# if $N is 0, then the entire line is used for comparison. 

awk -v N=$N -v lookup="$LOOKUP" ' 
    BEGIN { while (getline < lookup) { dictionary[$0]=$0 } } 
    !($N in dictionary) {print}' 

(Un autre avantage de cette approche est qu'il est facile de modifier le critère de comparaison, par exemple pour couper tête et de queue blanc.)

+0

Il est plus difficile à utiliser dans un scénario de plateforme croisée que l'autre. Cependant chapeau pour l'effort de performance – twobob

0

Vous pouvez utiliser Python:

python -c ' 
lines_to_remove = set() 
with open("file B", "r") as f: 
    for line in f.readlines(): 
     lines_to_remove.add(line.strip()) 

with open("file A", "r") as f: 
    for line in [line.strip() for line in f.readlines()]: 
     if line not in lines_to_remove: 
      print(line) 
'