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

160

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

Quelle commande utiliserais-je pour supprimer toutes les adresses qui apparaissent 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 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é qu'une seule commande en ligne qui m'a donné une erreur avec un mauvais délimiteur.

Toute aide serait très appréciée! Quelqu'un va sûrement proposer un one-liner intelligent, mais je ne suis pas l'expert des coquilles.

slhck
la source
1
La plupart si les réponses ici concernent des fichiers triés et que la plus évidente est manquante, ce qui n'est bien sûr pas de votre faute, mais cela rend l'autre plus généralement utile.
tripleee

Réponses:

204

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

comm -23 file1 file2

-23supprime les lignes qui se trouvent dans les deux fichiers, ou uniquement dans le fichier 2. Si les fichiers ne sont pas triés, passez-les d' sortabord ...

Voir la page de manuel ici

L'archétype de Paul
la source
8
comm -23 file1 file2 > file3affichera le contenu du fichier1 pas du fichier2, vers le fichier3. Et puis mv file3 file1effacerait enfin le contenu redondant dans file1.
Spectral
2
Sinon, utilisez comm -23 file1 file2 | sponge file1. Aucun nettoyage nécessaire.
Socowi
Le lien de la page de manuel ne se charge pas pour moi - alternative: linux.die.net/man/1/comm
Felix Rabe
@Socowi Qu'est-ce que l'éponge? Je n'ai pas ça sur mon système. (macos 10.13)
Felix Rabe
@FelixRabe, eh bien, c'est ennuyeux. Remplacé par votre lien. Merci
The Archetypal Paul
85

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

  • fonctionne sur des fichiers non triés
  • maintient l'ordre
  • est POSIX

Exemple:

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

cat <<EOF > B
0
1
EOF

grep -Fvxf B A

Production:

b
a
01
b

Explication:

  • -F: utilisez des chaînes littérales au lieu du BRE par défaut
  • -x: ne considère que les correspondances qui correspondent à la ligne entière
  • -v: impression non correspondante
  • -f file: prendre des modèles 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: Un moyen rapide de trouver des lignes dans un fichier qui ne sont pas dans un autre?

Voici une automatisation rapide pour le fonctionnement en ligne:

remove-lines() (
  remove_lines="$1"
  all_lines="$2"
  tmp_file="$(mktemp)"
  grep -Fvxf "$remove_lines" "$all_lines" > "$tmp_file"
  mv "$tmp_file" "$all_lines"
)

GitHub en amont .

usage:

remove-lines lines-to-remove remove-from-this-file

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

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
la source
55

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

Retour

A
C

Comment ça marche?

NR==FNR{a[$0];next} idiom sert à stocker le premier fichier dans un tableau associatif sous forme de clés pour un test "contient" ultérieur.

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

a[$0] ajoute la ligne courante au tableau associatif comme clé, notez que cela 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 (s) fichier (s) suivant (s), inest un test contient, ici il vérifie si la ligne actuelle 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 généralement pas écrite explicitement.

Notez que cela peut maintenant être utilisé pour supprimer les mots de la 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 ...
Karakfa
la source
toutes les notes à ce sujet. Pour l'utiliser sur la ligne de commande dans GnuWin32 sous Windows, remplacez les grignotages simples par des guillemets doubles. fonctionne un régal. Merci beaucoup.
twobob
Cela fonctionne, mais comment vais-je pouvoir rediriger la sortie vers fileA sous la forme de A (avec une nouvelle ligne) B
Anand Builders
Je suppose que vous voulez dire A\nC, écrivez d'abord dans un fichier temporaire et écrasez le fichier d'origine... > tmp && mv tmp fileA
karakfa
Toutes mes marques là-dessus de ma part aussi. Cette awk prend 1 seconde pour traiter un fichier avec 104000 entrées: +1:
MitchellK
Lorsque vous utilisez ceci dans des scripts, assurez-vous d'abord de vérifier qu'il fileBn'est pas vide (0 octet de long), car si c'est le cas, vous obtiendrez un résultat vide au lieu du contenu attendu de fileA. (Cause: FNR==NRs'appliquera à ce moment- fileAlà.)
Peter Nowee
18

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)
Suspendu jusqu'à nouvel ordre.
la source
7

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-formatest pour les lignes qui sont dans le fichier b mais pas dans a --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. %Lfait en sorte que la ligne soit imprimée exactement.

man diff

pour plus de détails

aec
la source
1
Vous dites que cela fonctionnera à moins que les fichiers ne soient triés. Quels problèmes surviennent s'ils sont triés? Et s'ils sont partiellement triés?
Carlos Macasaet
1
C'était en réponse à la solution ci-dessus qui suggérait l'utilisation de la commcommande. commexige 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
aec
7

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

Cette formulation permet également la possibilité qu'un seul champ particulier ($ N) dans le fichier d'entrée soit utilisé 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 les espaces blancs de début et de fin.)

de pointe
la source
Ceci est plus difficile à utiliser dans un scénario de plate-forme croisée à cas d'angle que l'autre doublure. Cependant, chapeau bas pour l'effort de performance
twobob
2

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)
'
Bonjour au revoir
la source
2

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

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

Darpan
la source
-1

Pour supprimer les lignes communes entre deux fichiers, vous pouvez utiliser la commande grep, comm ou join.

grep ne fonctionne que pour les petits fichiers. Utilisez -v avec -f.

grep -vf file2 file1 

Cela affiche les lignes du fichier1 qui ne correspondent à aucune ligne du fichier2.

comm est une commande utilitaire qui fonctionne sur des fichiers triés lexicalement. Il prend deux fichiers en entrée et produit trois colonnes de texte en sortie: des lignes uniquement dans le premier fichier; lignes uniquement dans le deuxième fichier; et des lignes dans les deux fichiers. Vous pouvez supprimer l'impression de n'importe quelle colonne en utilisant l'option -1, -2 ou -3 en conséquence.

comm -1 -3 file2 file1

Cela affiche les lignes du fichier1 qui ne correspondent à aucune ligne du fichier2.

Enfin, il existe join, une commande utilitaire qui effectue une jointure d'égalité sur les fichiers spécifiés. Son option -v permet également de supprimer les lignes communes entre deux fichiers.

join -v1 -v2 file1 file2
Aakarsh Gupta
la source
Tous ces éléments ont déjà été donnés dans d'autres réponses. Votre grep a besoin d'un -F, ou vous obtiendrez des résultats étranges lorsque les lignes ressemblent à des expressions régulières
The Archetypal Paul