198 votes

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

J'ai un grand fichier A (composé de courriers électroniques), une ligne pour chaque courrier. J'ai également un autre fichier B qui contient une autre série de messages.

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

Ainsi, si le fichier A contient :

A
B
C

et le dossier B contenu :

B    
D
E

Dans ce cas, le fichier A devrait être laissé avec :

A
C

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

Toute aide serait très appréciée ! Quelqu'un trouvera certainement une solution intelligente, mais je ne suis pas un expert en shell.

1 votes

1 votes

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

237voto

Paul Points 18124

Si les fichiers sont triés (c'est le cas dans votre exemple) :

comm -23 file1 file2

-23 supprime les lignes qui se trouvent dans les deux fichiers ou seulement dans le fichier 2. Si les fichiers ne sont pas triés, ils sont acheminés par l'intermédiaire de sort d'abord...

Voir le page de manuel ici

11 votes

comm -23 file1 file2 > file3 permet d'afficher dans le fichier 3 le contenu du fichier 1 et non celui du fichier 2. Et ensuite mv file3 file1 effacerait finalement le contenu redondant du fichier 1.

3 votes

Vous pouvez également utiliser comm -23 file1 file2 | sponge file1 . Aucun nettoyage n'est nécessaire.

0 votes

Le lien vers la page de l'homme ne se charge pas pour moi - alternative : linux.die.net/man/1/comm

112voto

Ciro Santilli Points 3341

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

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 du BRE par défaut.
  • -x : ne prend en compte que les correspondances qui correspondent à l'ensemble de la ligne
  • -v : imprimer les données non concordantes
  • -f file : prend des motifs dans le 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 est également importante, voir : Comment trouver rapidement des lignes dans un fichier qui ne se trouvent pas dans un autre ?

Voici une automatisation rapide pour un 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 .

l'utilisation :

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

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

70voto

karakfa Points 604

Awk à la rescousse !

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

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

retours

A
C

Comment cela fonctionne-t-il ?

NR==FNR{a[$0];next} consiste à stocker le premier fichier dans un tableau associatif comme clés pour un test "contains" 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 du fichier courant (FNR).

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

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

Notez qu'il est désormais possible d'utiliser cette fonction pour supprimer les mots figurant sur 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 ...

19voto

Dennis Williamson Points 105818

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)

9voto

aec Points 102

Vous pouvez le faire si vos fichiers ne sont pas triés.

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

--new-line-format concerne les lignes qui se trouvent dans le fichier b mais pas dans le fichier a --old-.. concerne les lignes qui se trouvent dans le fichier a mais pas dans le fichier 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

1 votes

Vous dites que cela fonctionnera si les fichiers ne sont pas triés. Quels sont les problèmes qui se posent si les fichiers sont triés ? Que se passe-t-il s'ils sont partiellement triés ?

1 votes

C'était en réponse à la solution ci-dessus qui suggérait l'utilisation de comm commande. comm exige que les fichiers soient triés, donc s'ils le sont, vous pouvez également utiliser cette solution. Vous pouvez toutefois utiliser cette solution, que le fichier soit trié ou non

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X