Vergleichen Sie zwei durch Trennzeichen getrennte Dateien und geben Sie die Unterschiede aus

Vergleichen Sie zwei durch Trennzeichen getrennte Dateien und geben Sie die Unterschiede aus

Ich habe hier in der Vergangenheit einige Male mit großem Erfolg ähnliche Fragen gestellt, doch jetzt haben sich meine Anforderungen leicht geändert und ich habe Schwierigkeiten, genau das Ergebnis zu erhalten, nach dem ich suche.

Ich möchte 2 vergleichenähnlichabgegrenzte Dateien, aber sie haben eine unterschiedliche Zeilenanzahl und einige Duplikate. Die Dateien haben identische Header.

file1.txt

mem_id     date     time     building
aa1         bb1      cc1     dd1
aa2         bb2      cc2     dd2
aa3         bb3      ccx3    dd3
aa4         bb4      cc4     dd4
aa5         bb5      cc5     dd5

file2.txt

mem_id     date     time     building
aa1         bby1     cc1     ddy1
aa2         bb2      cc2     dd2  
aa3         bb3      cc3     dd3
aa4         bb4      cc4     dd4
aa4         bb4a     cc4a    dd4a

Sie werden sehen, dass es 4 Unterschiede gibt:

1- File2, mem_id aa1 hat ein „y“ sowohl in der Spalte „Datum“ als auch in der Spalte „Gebäude“

2- File1, mem_id aa3 hat ein „x“ in der Spalte „Zeit“

3- Datei1, hat eine Mem_ID aa5

4- File2, mem_id aa4 hat 2 Einträge

Ich möchte ein Skript ausführen, das nur die Unterschiede zwischen den beiden Dateien ausgibt (identische Zeilen überspringen). Alles, was ich versucht habe, bleibt an den doppelten oder übersprungenen Zeilen hängen, wodurch die Ausgabe in der gesamten Datei durcheinandergerät. Wenn alle Zeilen übereinstimmen, funktioniert der folgende Code gut:

current_code

awk -F ',' 'BEGIN {IGNORECASE = 1} NR==1 {for (i=1; i<=NF; i++)    header[i] = $i}NR==FNR {for (i=1; i<=NF; i++) {A[i,NR] = $i} next}{  for (i=1; i<=NF; i++) if (A[i,FNR] != $i) print header[1]"#-"$1": " header[i] "- " ARGV[1] " value= ", A[i,FNR]" / " ARGV[2] " value= "$i}'

desired_output.txt

Mem_id#-aa1 : date-  file1.txt value = bb1 / file2.txt value= bby1
Mem_id#-aa1 : building-  file1.txt value = dd1 / file2.txt value= ddy1
Mem_id#-aa3 : time-  file1.txt value = ccx3 / file2.txt value= dd3
Mem_id#-aa4 : date-  file1.txt value =        / file2.txt value= bb4a
Mem_id#-aa4 : time-  file1.txt value =        / file2.txt value= cc4a
Mem_id#-aa4 : building-  file1.txt value =        / file2.txt value= dd4a
Mem_id#-aa5 : date-  file1.txt value = bb5 / file2.txt value= 
Mem_id#-aa5 : time-  file1.txt value =  cc5 / file2.txt value= 
Mem_id#-aa5 : building-  file1.txt value =  dd5 / file2.txt value= 

Antwort1

Das folgende Python-Programm sollte das tun, was Sie möchten, oder etwas sehr Ähnliches.

  • In der desired_output.txt3. Zeile scheint ein Fehler zu sein:

    Mem_id#-aa3 : time-  file1.txt value = ccx3 / file2.txt value= dd3
    

    der dd3 should probably becc3`

    Abgesehen davon stimmt die Ausgabe des Programms mit Ausnahme der Leerzeichen überein, die in Ihrer Beispielausgabe etwas unregelmäßig erscheinen.

  • Die Eingabe wird als nach Schlüssel (memid) sortiert betrachtet.

  • Das Programm puffert standardmäßig 4 Zeilen (max_diff + 1), während es versucht, zu synchronisieren. Wenn keiner der Schlüssel direkt in diesem Puffer mit dem „aktuellen“ Schlüssel übereinstimmt und vv, werden beide als nicht übereinstimmend betrachtet und gedruckt, und das nächste Paar wird ausprobiert. Wenn ein Schlüssel gefunden wird, werden zuerst die nicht übereinstimmenden Elemente im anderen Puffer oder ausgegeben.
  • Die Beispieleingabe ist hinsichtlich des zu erwartenden Verhaltens etwas eingeschränkt, wenn die erste und die zweite Zeile zweimal (oder öfter) dieselbe Memid aufweisen.

    In output()versuche ich, alle Zeilen abzugleichen und alle Übereinstimmungen auszulesen (sowohl von links als auch von rechts). Daher ist die Reihenfolge der übereinstimmenden Zeilen innerhalb desselben Memids nicht wichtig. Wenn links oder rechts oder beide leer sind, ist das Drucken danach einfach (insbesondere, wenn beide leer sind). Im Übrigen vergleiche ich jede verbleibende Zeile von links nach rechts.

  • Der fmtString „in“ line_out()bestimmt die Ausgabe, Sie können diesen beliebig ändern/neu anordnen.

#! /usr/bin/env python
# coding: utf-8
# http://unix.stackexchange.com/q/161913/33055

from __future__ import print_function
from collections import OrderedDict
from logging import debug

import sys


class RowBuffer:
    def __init__(self, file_name, delim=None, max_diff=3):
        """delim is the character that is used for splitting input.
        None->whitespace
        """
        self._verbose = 0
        self._file_name = file_name
        self._fp = open(self._file_name)
        self._delim = delim
        self._max_diff = max_diff
        self._head = self._fp.readline().split(delim)
        # the buffer consists of a maximum of max_diff entries
        # the keys are the first items of a row, the value a list
        # of all other items on that row
        self._buffer = OrderedDict()
        self.fill_buffer()

    def compare(self, rb):
        """check if self._buffer"""
        if self._head != rb._head:
            print('headings differ:\n  {}\n  {}'.format(
                self._head, rb._head))
        while self._buffer:
            l = self.get()
            try:
                r = rb.get()
            except KeyError:
                debug('only left %s', l[0])
                self.output(l, None, rb)
                break
            if l[0] == r[0]:
                debug('compare vals %s', l[0])
                self.output(l, r, rb)
                continue
            if l[0] in rb:
                # left key in right, but not at top
                # output right until top keys are same
                while l[0] != r[0]:
                    debug('only right %s', r[0])
                    self.output(None, r, rb)
                    r = rb.get()
                self.output(l, r, rb)
                continue
            if r[0] in self:
                # right key in left, but not at top
                # output left until top keys are same
                while l[0] != r[0]:
                    debug('only left %s', l[0])
                    self.output(l, None, rb)
                    l = self.get()
                self.output(l, r, rb)
                continue
            # neither found: output both
            debug('neither left in right nor vv %s %s', l[0], r[0])
            self.output(l, None, rb)
            self.output(None, r, rb)
        while rb._buffer:  # remaining in right file
            r = rb.get()
            debug('only right %s', r[0])
            self.output(None, r, rb)

    def output(self, l, r, right):
        fmt1 = '{col0_header}#-{col0_value} : {col_header}-  ' \
            '{left_file_name} value = {left_value} / ' \
            '{right_file_name} value= {right_value}'
        d = dict(
            col0_header=self._head[0],
            left_file_name=self._file_name,
            right_file_name=right._file_name,
        )
        if l is not None and r is not None:
            # one or more values on both sides, compare all lines on the
            # left with all on the right remove any matching pairs
            match = {}  # left index to right index
            for lidx, lv in enumerate(l[1]):
                for ridx, rv in enumerate(r[1]):
                    if lv == rv:
                        if lidx not in match:
                            match[lidx] = ridx
            # pop from back of list, not invalidate index
            for lidx in sorted(match, reverse=True):
                l[1].pop(lidx)
            for ridx in sorted(match.values(), reverse=True):
                r[1].pop(lidx)
        if r is None or not r[1]:
            for lv in l[1]:
                for idx, k in enumerate(self._head[1:]):
                    self.line_out(d, col0_value=l[0], col_header=k,
                                  left_value=lv[idx], right_value='    ')
            return
        if l is None or not l[1]:
            for rv in r[1]:
                for idx, k in enumerate(self._head[1:]):
                    self.line_out(d, col0_value=l[0], col_header=k,
                                  left_value='    ', right_value=rv[idx])
            return
        # print non matching
        for lv in l[1]:
            for rv in r[1]:
                for idx, k in enumerate(self._head[1:]):
                    if lv[idx] == rv[idx]:
                        continue  # same value
                    self.line_out(d, col0_value=l[0], col_header=k,
                                  left_value=lv[idx], right_value=rv[idx])

    def line_out(self, d, **kw):
        # manipulate and print output
        # the fields of the format string can be arbitrarily arranged
        # as long as the field names (between {} match)
        fmt = '{col0_header}#-{col0_value} : {col_header}-  ' \
            '{left_file_name} value = {left_value} / ' \
            '{right_file_name} value= {right_value}'
        d1 = d.copy()
        d1.update(kw)
        s = fmt.format(**d1)
        # s = s.rstrip()
        s = s[0].upper() + s[1:]  # sample output doesn't match input
        print(s)

    def get(self):
        item = self._buffer.popitem(last=False)
        self.fill_buffer()
        return item

    def fill_buffer(self):
        if self._fp is None:
            return
        while len(self._buffer) < self._max_diff:
            row = self._fp.readline().split(self._delim)
            if not row:
                self._fp.close()
                self._fp = None
                return
            entry = self._buffer.setdefault(row[0], [])
            entry.append(row[1:])

    def __contains__(self, key):
        self.fill_buffer()
        return key in self._buffer

rb1 = RowBuffer(sys.argv[1])
rb2 = RowBuffer(sys.argv[2])
rb1.compare(rb2)

Antwort2

Dies ist eine (alles andere als elegante) Teillösung für Ihr Problem. Sie verwendet die erste Spalte als ID-Spalte (es muss nicht die erste sein, aber Sie müssen auf jeden Fall eine haben) und führt eine dritte Dimension ein, suffixum mehrere Vorkommen desselben Schlüssels zu speichern. Am Ende versucht sie, die Schlüssel von Datei 2 zu finden, die in Datei 1 nicht gefunden wurden.

BEGIN {
    IGNORECASE = 1
} 

NR==1 {
    for (i = 1; i <= NF; i++)    
        header[i] = $i
    suffix = 0
    previous_key=""
}

NR==FNR {
    if ($1 == previous_key) {
        suffix = suffix + 1
        max_suffix[$1] = suffix
    } else
        suffix = 0
    for (i = 1; i <= NF; i++) {
        A[$1,suffix,i] = $i
    } 
    key_count[$1] = key_count[$1] + 1
    previous_key = $1
    next
}

{  
    if ($1 == previous_key)
        suffix = suffix + 1
    else
        suffix = 0
    previous_key = $1
    if (A[$1,suffix,1] != "") {
        for (i = 2; i <= NF; i++) 
            if (A[$1,suffix,i] != $i) {
                print header[1]"#-"$1": " header[i] "- " ARGV[1] " value= ", A[$1,suffix,i]" / " ARGV[2] " value= "$i
            }
        key_count[$1] = key_count[$1] - 1
    }
    else
        for (i = 2; i <= NF; i++) 
            print header[1]"#-"$1": " header[i] "- " ARGV[1] " value= ", " / " ARGV[2] " value= "$i
}

END {
    for (missing_key in key_count) 
        if (key_count[missing_key] > 0) {            
            for (suffix = max_suffix[missing_key] - key_count[missing_key] + 1; suffix <= C[missing_key]; suffix++) 
                for (i = 2; i <= NF; i++) 
                    print header[1]"#-"missing_key": " header[i] "- " ARGV[1] " value= ", A[missing_key,suffix,i] " / " ARGV[2] " value= "
        }           
}

Es gibt jedoch einen Vorbehalt: Nicht übereinstimmende Einträge in Datei 2 werden immer am Ende gedruckt und nicht entsprechend der Position in der Datei sortiert. Außerdem ist die Sortierung dieser Zeilen beliebig. Ich vermute, dass dies dadurch behoben werden kann, dass das Ergebnis in einen sortBefehl weitergeleitet wird.

verwandte Informationen