J'ai deux gros fichiers (ensembles de noms de fichiers). Environ 30 000 lignes dans chaque fichier. J'essaie de trouver un moyen rapide de trouver des lignes dans le fichier1 qui ne sont pas présentes dans le fichier2.
Par exemple, s'il s'agit de file1:
line1
line2
line3
Et voici file2:
line1
line4
line5
Alors mon résultat / sortie devrait être:
line2
line3
Cela marche:
grep -v -f file2 file1
Mais c'est très, très lent lorsqu'il est utilisé sur mes gros fichiers.
Je soupçonne qu'il existe un bon moyen de le faire en utilisant diff (), mais la sortie ne devrait être que les lignes, rien d'autre, et je n'arrive pas à trouver un commutateur pour cela.
Quelqu'un peut-il m'aider à trouver un moyen rapide de le faire, en utilisant bash et les binaires linux de base?
EDIT: Pour faire suite à ma propre question, c'est la meilleure façon que j'ai trouvée jusqu'à présent en utilisant diff ():
diff file2 file1 | grep '^>' | sed 's/^>\ //'
Il doit certainement exister un meilleur moyen?
awk 'NR==FNR{a[$0];next}!($0 in a)' file2 file1 > out.txt
cat file1 file2 file2 | sort | uniq --unique
voir ma réponse ci-dessous.Réponses:
Vous pouvez y parvenir en contrôlant le formatage des anciennes / nouvelles / lignes inchangées dans la
diff
sortie GNU :Les fichiers d'entrée doivent être triés pour que cela fonctionne. Avec
bash
(etzsh
), vous pouvez trier sur place avec la substitution de processus<( )
:Dans les lignes ci-dessus , les lignes nouvelles et inchangées sont supprimées, donc seules les lignes modifiées (c'est-à-dire les lignes supprimées dans votre cas) sont sorties. Vous pouvez également utiliser quelques
diff
options d' autres solutions n'offrent pas, comme-i
pour ignorer la casse, ou diverses options (espaces blancs-E
,-b
,-v
etc) pour la correspondance moins stricte.Explication
Les options
--new-line-format
,--old-line-format
et--unchanged-line-format
vous permettent de contrôler la façon dediff
formater les différences, comme pour lesprintf
spécificateurs de format. Ces options mettent en forme respectivement les lignes nouvelles (ajoutées), anciennes (supprimées) et inchangées . Mettre un à vide "" empêche la sortie de ce type de ligne.Si vous connaissez le format diff unifié , vous pouvez le recréer en partie avec:
Le
%L
spécificateur est la ligne en question, et nous préfixons chacun avec "+" "-" ou "", commediff -u
(notez qu'il ne produit que des différences, il manque les lignes---
+++
et@@
en haut de chaque changement groupé). Vous pouvez également l'utiliser pour faire d'autres choses utiles comme numéroter chaque ligne avec%dn
.La
diff
méthode (avec d'autres suggestionscomm
etjoin
) ne produit que la sortie attendue avec une entrée triée , bien que vous puissiez utiliser<(sort ...)
pour trier sur place. Voici unawk
script simple (nawk) (inspiré des scripts liés dans la réponse de Konsolebox) qui accepte les fichiers d'entrée commandés arbitrairement et génère les lignes manquantes dans l'ordre dans lequel elles se produisent dans file1.Cela stocke le contenu entier de file1 ligne par ligne dans un tableau indexé de numéro de ligne
ll1[]
, et le contenu entier de file2 ligne par ligne dans un tableau associatif indexé de contenu de ligness2[]
. Une fois les deux fichiers lus, parcourezll1
et utilisez l'in
opérateur pour déterminer si la ligne dans file1 est présente dans file2. (Cela aura une sortie différente de ladiff
méthode s'il y a des doublons.)Dans le cas où les fichiers sont suffisamment volumineux pour que leur stockage entraîne un problème de mémoire, vous pouvez échanger la CPU contre de la mémoire en stockant uniquement file1 et en supprimant les correspondances en cours de lecture.
Ce qui précède stocke l'intégralité du contenu de file1 dans deux tableaux, l'un indexé par numéro de ligne
ll1[]
, l'autre indexé par contenu de ligness1[]
. Lorsque le fichier2 est lu, chaque ligne correspondante est supprimée dell1[]
etss1[]
. À la fin, les lignes restantes du fichier1 sont sorties, préservant l'ordre d'origine.Dans ce cas, avec le problème indiqué, vous pouvez également diviser et conquérir à l' aide de GNU
split
(le filtrage est une extension GNU), des exécutions répétées avec des morceaux de fichier1 et la lecture complète de fichier2 à chaque fois:Notez l'utilisation et le placement du
-
sensstdin
sur lagawk
ligne de commande. Ceci est fourni par àsplit
partir de file1 en morceaux de 20000 lignes par appel.Pour les utilisateurs sur les systèmes non-GNU, il est presque certainement un paquet coreutils GNU , vous pouvez obtenir, y compris sur OSX dans le cadre des d' Apple Xcode outils qui fournit GNU
diff
,awk
, mais seulement un POSIX / BSDsplit
plutôt que d' une version GNU.la source
diff
: en général, les fichiers d'entrée seront différents, 1 est retourné pardiff
dans ce cas. Considérez cela comme un bonus ;-) Si vous testez dans un script shell 0 et 1 sont des codes de sortie attendus, 2 indique un problème.man diff
. Merci!La commande comm (abréviation de "common") peut être utile
comm - compare two sorted files line by line
Le
man
fichier est en fait assez lisible pour cela.la source
comm
a également une option pour vérifier que l'entrée est triée--check-order
(ce qu'il semble faire de toute façon, mais cette option entraînera une erreur au lieu de continuer). Mais pour trier les fichiers, faites simplement:com -23 <(sort file1) <(sort file2)
et ainsi de suitecomm
ne fonctionnait pas du tout. Il m'a fallu un certain temps pour comprendre qu'il s'agit des fins de ligne: même les lignes qui semblent identiques sont considérées comme différentes si elles ont des fins de ligne différentes. La commandedos2unix
peut être utilisée pour convertir les fins de ligne CRLF en LF uniquement.Comme l'a suggéré konsolebox, la solution posters grep
fonctionne très bien (rapidement) si vous ajoutez simplement l'
-F
option, pour traiter les modèles comme des chaînes fixes au lieu d'expressions régulières. J'ai vérifié cela sur une paire de ~ 1000 listes de fichiers de ligne que j'ai dû comparer. Avec-F
cela a pris 0,031 s (réel), tandis que sans cela a pris 2,278 s (réel), lors de la redirection de la sortie grep verswc -l
.Ces tests ont également inclus le
-x
commutateur, qui est une partie nécessaire de la solution afin d'assurer une précision totale dans les cas où le fichier2 contient des lignes qui correspondent à une partie, mais pas à la totalité, d'une ou plusieurs lignes du fichier1.Ainsi, une solution qui ne nécessite pas de tri des entrées, est rapide, flexible (respect de la casse, etc.) est:
Cela ne fonctionne pas avec toutes les versions de grep, par exemple, il échoue dans macOS, où une ligne dans le fichier 1 sera affichée comme non présente dans le fichier 2, même si elle l'est, si elle correspond à une autre ligne qui en est une sous-chaîne . Alternativement, vous pouvez installer GNU grep sur macOS afin d'utiliser cette solution.
la source
-F
ça ça ne va pas bien.file2
.-x
option utilise apparemment plus de mémoire. Avec unfile2
contenu de 180 millions de mots de 6 à 10 octets, mon processus est arrivéKilled
sur une machine de 32 Go de RAM ...quelle est la vitesse de tri et de diff?
la source
Si vous manquez d '"outils sophistiqués", par exemple dans une distribution Linux minimale, il existe une solution avec juste
cat
,sort
etuniq
:Tester:
Ceci est également relativement rapide par rapport à
grep
.la source
--unique
option. Vous devriez pouvoir utiliser l' option POSIX standardisée pour cela:| uniq -u
seq 1 1 7
crée des nombres de 1, avec incrément 1, jusqu'à 7, c'est-à-dire 1 2 3 4 5 6 7. Et là, c'est votre 2!Le
-t
s'assure qu'il compare toute la ligne, si vous aviez un espace dans certaines lignes.la source
comm
, lesjoin
deux lignes d'entrée doivent être triées sur le champ sur lequel vous effectuez l'opération de jointure.Vous pouvez utiliser Python:
la source
Utilisation
combine
demoreutils
paquet, un utilitaire qui prend en charge des ensemblesnot
,and
,or
,xor
opérationsc'est-à-dire me donner des lignes qui sont dans le fichier1 mais pas dans le fichier2
OU donnez-moi des lignes dans le fichier1 moins des lignes dans le fichier2
Remarque:
combine
trie et trouve des lignes uniques dans les deux fichiers avant d'effectuer une opération maisdiff
ne le fait pas. Vous pouvez donc trouver des différences entre la sortie dediff
etcombine
.Donc, en fait, vous dites
Recherchez des lignes distinctes dans les fichiers file1 et file2, puis donnez-moi des lignes dans le fichier1 moins les lignes dans le fichier2
D'après mon expérience, c'est beaucoup plus rapide que les autres options
la source
L'utilisation de fgrep ou l'ajout de l'option -F à grep pourrait aider. Mais pour des calculs plus rapides, vous pouvez utiliser Awk.
Vous pouvez essayer l'une de ces méthodes Awk:
http://www.linuxquestions.org/questions/programming-9/grep-for-huge-files-826030/#post4066219
la source
La façon dont je le fais habituellement consiste à utiliser le
--suppress-common-lines
drapeau, mais notez que cela ne fonctionne que si vous le faites au format côte à côte.diff -y --suppress-common-lines file1.txt file2.txt
la source
J'ai trouvé que pour moi, l'utilisation d'une instruction de boucle normale si et pour fonctionnait parfaitement.
la source
grep
résultats se développe sur plusieurs mots ou si l'une de vosfile2
entrées peut être traitée par le shell comme un glob.