Comparez deux fichiers ligne par ligne et générez la différence dans un autre fichier

121

Je veux comparer file1 avec file2 et générer un fichier3 qui contient les lignes dans file1 qui ne sont pas présentes dans file2.

Balualways
la source
J'ai essayé diff mais cela génère des nombres et d'autres symboles devant différentes lignes, ce qui me rend difficile la comparaison des fichiers.
Dim

Réponses:

216

diff (1) n'est pas la réponse, mais comm (1) l'est.

NAME
       comm - compare two sorted files line by line

SYNOPSIS
       comm [OPTION]... FILE1 FILE2

...

       -1     suppress lines unique to FILE1

       -2     suppress lines unique to FILE2

       -3     suppress lines that appear in both files

Alors

comm -2 -3 file1 file2 > file3

Les fichiers d'entrée doivent être triés. Si ce n'est pas le cas, triez-les d'abord. Cela peut être fait avec un fichier temporaire, ou ...

comm -2 -3 <(sort file1) <(sort file2) > file3

à condition que votre shell supporte la substitution de processus (bash le fait).

sorpigal
la source
1
Rappelez-vous que deux fichiers doivent être triés et sont uniques
andy
6
Vous pouvez regrouper les options:comm -23
Paolo M
Que signifie «trié»? Que les lignes ont le même ordre? Ensuite, c'est probablement bien pour la plupart des cas d'utilisation - comme pour vérifier quelles lignes ont été ajoutées en comparant avec une version plus ancienne sauvegardée. Si les lignes nouvellement ajoutées ne peuvent pas être entre les lignes existantes, c'est plus un problème.
Egor Hans
@EgorHans: si le fichier contient par exemple des lignes contenant des entiers tels que "3 \ n1 \ n3 \ n2 \ n", les lignes doivent d'abord être réordonnées par ordre croissant ou décroissant, par exemple "\ 1 \ n2 \ n3 \ n3 \ n" avec des doublons adjacent. C'est "trié" et les deux fichiers doivent être triés de la même manière. Lorsque le fichier le plus récent a de nouvelles lignes, peu importe si elles sont «entre les lignes existantes» car après le tri elles ne sont pas, elles sont triées.
sorpigal
48

L'utilitaire Unix diffest conçu exactement dans ce but.

$ diff -u file1 file2 > file3

Consultez le manuel et Internet pour les options, les différents formats de sortie, etc.

Thanatos
la source
8
Cela ne fait pas le travail demandé; il insère un tas de caractères supplémentaires, même avec l'utilisation des commutateurs de ligne de commande suggérés dans d'autres réponses.
xenocyon le
20

Considérez ceci:
fichier a.txt:

abcd
efgh

fichier b.txt:

abcd

Vous pouvez trouver la différence avec:

diff -a --suppress-common-lines -y a.txt b.txt

La sortie sera:

efgh 

Vous pouvez rediriger la sortie dans un fichier de sortie (c.txt) en utilisant:

diff -a --suppress-common-lines -y a.txt b.txt > c.txt

Cela répondra à votre question:

"... qui contient les lignes du fichier1 qui ne sont pas présentes dans le fichier2."

Neilvert Noval
la source
2
Il y a deux limitations à cette réponse: (1) cela ne fonctionne que pour les lignes courtes (moins de 80 caractères par défaut, bien que cela puisse être modifié) et, plus important, (2) il ajoute un "<" à la fin de chaque ligne qui doit être supprimée avec un autre programme (par exemple awk, sed).
sergut
Dans de nombreux cas, vous voudrez également utiliser -d, ce qui fera de diffson mieux pour trouver le plus petit diff. -i, -E, -w, -BEt --suppress-blank-emptypeut aussi être utile de temps en temps, mais pas toujours. Si vous ne savez pas ce qui correspond à votre cas d'utilisation, essayez d' diff --helpabord (ce qui est généralement une bonne idée lorsque vous ne savez pas ce qu'une commande peut faire).
Egor Hans
De plus, en utilisant --line-format =% L, vous empêchez diff de générer des caractères supplémentaires (au moins, l'aide dit que cela fonctionne comme ça, mais sur le point de l'essayer).
Egor Hans
De plus, c'est plus court et semble fonctionner de la même manière stackoverflow.com/a/27667185/1179925
mrgloom
8

Parfois, diffc'est l'utilitaire dont vous avez besoin, mais parfois joinplus approprié. Les fichiers doivent être pré-triés ou, si vous utilisez un shell prenant en charge la substitution de processus comme bash, ksh ou zsh, vous pouvez effectuer le tri à la volée.

join -v 1 <(sort file1) <(sort file2)
Suspendu jusqu'à nouvel ordre.
la source
Vous devriez obtenir une médaille pour cela! C'était exactement ce que je cherchais ces 2 dernières heures
Zatarra
7

Essayer

sdiff file1 file2

Cela fonctionne généralement beaucoup mieux dans la plupart des cas pour moi. Vous voudrez peut-être trier les fichiers avant, si l'ordre des lignes n'est pas important (par exemple, certains fichiers de configuration de texte).

Par exemple,

sdiff -w 185 file1.cfg file2.cfg
Tagar
la source
1
Belle utilité! J'aime la façon dont il marque les lignes de différenciation. Rend beaucoup plus facile la comparaison des configurations. Ceci avec le tri est un combo mortel (par exemple sdiff <(sort file1) <(sort file2))
jmagnusson
3

Si vous avez besoin de résoudre cela avec coreutils, la réponse acceptée est bonne:

comm -23 <(sort file1) <(sort file2) > file3

Vous pouvez également utiliser sd (stream diff), qui ne nécessite pas de tri ni de substitution de processus et prend en charge des flux infinis, comme ceci:

cat file1 | sd 'cat file2' > file3

Probablement pas tellement d'un avantage sur cet exemple, mais considérez-le toujours; dans certains cas, vous ne pourrez pas utiliser commni grep -Fni diff.

Voici un article de blog que j'ai écrit sur les différents flux sur le terminal, qui présente sd.

mlg
la source
3

Pourtant, pas de grepsolution?

  • lignes qui n'existent que dans fichier2:

    grep -Fxvf file1 file2 > file3
  • lignes qui n'existent que dans fichier1:

    grep -Fxvf file2 file1 > file3
  • lignes qui existent dans les deux fichiers:

    grep -Fxf file1 file2 > file3
αғsнιη
la source
2

Beaucoup de réponses déjà, mais aucune d'entre elles n'est parfaite à mon humble avis. La réponse de Thanatos laisse quelques caractères supplémentaires par ligne et la réponse de Sorpigal nécessite que les fichiers soient triés ou pré-triés, ce qui peut ne pas être adéquat dans toutes les circonstances.

Je pense que la meilleure façon d'obtenir les lignes qui sont différentes et rien d' autre (pas de caractères supplémentaires, pas de re-commande) est une combinaison de diff, grepet awk(ou similaire).

Si les lignes ne contiennent aucun "<", une courte ligne unique peut être:

diff urls.txt* | grep "<" | sed 's/< //g'

mais cela supprimera toutes les instances de "<" (moins de, espace) des lignes, ce qui n'est pas toujours OK (par exemple le code source). L'option la plus sûre est d'utiliser awk:

diff urls.txt* | grep "<" | awk '{for (i=2; i<NF; i++) printf $i " "; print $NF}'

Cette ligne diffère les deux fichiers, puis filtre la sortie de style ed de diff, puis supprime le "<" de fin ajouté par diff. Cela fonctionne même si les lignes contiennent elles-mêmes des "<".

sergut
la source
1
comm ne nécessite pas de tri (dans les versions plus récentes?) - utilisez simplement --nocheck-order. J'utilise beaucoup cela lors de la manipulation de csvs depuis la CLI
ak5
2

Je suis surpris que personne n'ait mentionné diff -yde produire une sortie côte à côte , par exemple:

diff -y file1 file2 > file3

Et dans file3(les différentes lignes ont un symbole |au milieu):

same     same
diff_1 | diff_2
xtluo
la source
1

Utilisez l'utilitaire Diff et extrayez uniquement les lignes commençant par <dans la sortie

Capslockk
la source
0
diff a1.txt a2.txt | grep '> ' | sed 's/> //' > a3.txt

J'ai essayé presque toutes les réponses de ce fil, mais aucune n'était complète. Après quelques sentiers au-dessus, un a fonctionné pour moi. diff vous donnera une différence mais avec quelques charas spéciaux indésirables. où vos lignes de différence réelles commencent par «>». donc l'étape suivante consiste à grep les lignes commençant par '>' et suivies de la suppression de la même chose avec sed .

tollin jose
la source
1
C'est une mauvaise idée. Vous devrez également modifier les lignes commençant par <. Vous verrez cela si vous permutez l'ordre des fichiers d'entrée. Même si vous faisiez cela, vous voudriez omettre grepen utilisant plus de sed: `diff a1 a2 | sed '/> / s ///' 'Cela peut encore casser des lignes contenant >ou <dans la bonne situation et laisse toujours des lignes supplémentaires décrivant les numéros de ligne. Si vous voulez essayer cette approche une meilleure façon serait la suivante: diff -C0 a1 a2 | sed -ne '/^[+-] /s/^..//p'.
sorpigal
0

Vous pouvez utiliser diffavec le formatage de sortie suivant:

diff --old-line-format='' --unchanged-line-format='' file1 file2

--old-line-format='', désactiver la sortie pour fichier1 si la ligne était différente de la comparaison dans fichier2.
--unchanged-line-format='', désactivez la sortie si les lignes étaient identiques.

αғsнιη
la source