web-dev-qa-db-fra.com

Comment supprimer les lignes qui apparaissent sur le fichier B d'un autre fichier A?

J'ai un gros fichier A (composé de courriels), une ligne pour chaque courrier. J'ai aussi un autre fichier B qui contient un autre ensemble de mails.

Quelle commande utiliserais-je pour supprimer toutes les adresses figurant dans le fichier B du fichier A.

Donc, si le fichier A contenait:

A
B
C

et le fichier B contenait:

B    
D
E

Ensuite, le fichier A devrait être laissé avec:

A
C

Maintenant, je sais que c’est une question qui aurait pu être posée plus souvent, mais j’ai seulement trouvé ne commande en ligne qui m’a donné une erreur avec un mauvais délimiteur.

Toute aide serait très appréciée! Quelqu'un créera sûrement une ligne intelligente, mais je ne suis pas l'expert de Shell.

135
slhck

Si les fichiers sont triés (ils sont dans votre exemple):

comm -23 file1 file2

-23 supprime les lignes qui se trouvent dans les deux fichiers ou uniquement dans le fichier 2. Si les fichiers ne sont pas triés, dirigez-les vers sort en premier ...

Voir le page de manuel ici

181

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

  • fonctionne sur des fichiers non triés
  • maintient la commande
  • est 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: utilise des chaînes littérales au lieu du BRE par défaut
  • -x: ne considère que les correspondances qui correspondent à toute la ligne
  • -v: impression non concordante
  • -f file: prendre des motifs du fichier donné

Cette méthode est plus lente sur les fichiers pré-triés que les autres méthodes, car elle est plus générale. Si la vitesse compte également, voir: moyen rapide de trouver des lignes dans un fichier qui ne sont pas dans un autre?

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

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

résultats

A
C

Comment ça marche?

L'argument NR==FNR{a[$0];next} Sert à stocker le premier fichier d'un tableau associatif en tant que clés pour un test "contient" ultérieur.

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

a[$0] Ajoute la ligne actuelle au tableau associatif en tant que clé. Notez que cela se comporte comme un ensemble, dans lequel il n'y aura pas de valeurs dupliquées (clés).

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

Notez que cela peut maintenant être utilisé pour supprimer des mots de la liste noire.

$ awk '...' badwords allwords > goodwords

avec une légère modification, 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 ...
46
karakfa

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é-triés:

join -v 1 <(sort fileA) <(sort fileB)
17
Dennis Williamson

Vous pouvez le faire à moins que vos fichiers ne soient trié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 pour les lignes qui sont dans le fichier a mais pas dans b --unchanged-.. est pour les lignes qui sont dans les deux. %L fait en sorte que la ligne soit imprimée exactement.

man diff

pour plus de détails

6
aec

Ce raffinement de la réponse Nice de @ karakfa pourrait être nettement plus rapide pour les très gros fichiers. Comme pour cette réponse, aucun fichier n'a besoin d'être trié, mais la rapidité est assurée par les tableaux associatifs de awk. Seul le fichier de recherche est conservé en mémoire.

Cette formulation permet également de n’utiliser qu’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 réside dans le fait qu’il est facile de modifier le critère de comparaison, par exemple, de supprimer les espaces blancs de début et de fin.)

6
peak

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)
'
2
HelloGoodbye

Vous pouvez utiliser - diff fileA fileB | grep "^>" | cut -c3- > fileA

Cela fonctionnera pour les fichiers qui ne sont pas triés également.

2
Darpan