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.txt
3. Zeile scheint ein Fehler zu sein:Mem_id#-aa3 : time- file1.txt value = ccx3 / file2.txt value= dd3
der
dd3 should probably be
cc3`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
fmt
String „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, suffix
um 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 sort
Befehl weitergeleitet wird.