Nobody0815
Anmeldungsdatum: 29. April 2020
Beiträge: Zähle...
|
Hallo, hoffentlich kann mir jemand helfen! ☺ Ich habe zwei Dateien (1_liste, 2_liste).
Inhalt der Dateien: 1_liste
AU001Krueger83747
AU003Meier03948
AU002Schulze09447
AU004Mueller0013848
AU006Schneider0404003
AU005Weber847847005
AU007Schmidt74904949 2_liste
AU002Emilia948948
AU005Liam93765
AU001Anna0038383
AU006Jonas363355
AU003Leonie37346
AU004Paul49494 Die beiden Listen sollen sortiert, relevante Informationen (Namen) herausgeschnitten und zusammengeführt werden.
Es sollen nur Namen zusammengeführt werden, wo Vor- und Nachname existiert (AU001-AU005). Zu AU007 (Schmidt) gibt es keinen Vornamen (2_liste) und wird nicht berücksichtigt. Ergebnis soll so aussehen:
Anna Krueger
Emilia Schulze
Leonie Meier
Paul Mueller
Liam Weber Mein (nicht funktionierender) Ansatz: cat 1_liste | awk '
(FILENAME=="-"){substr($0,1,3)=AU1, substr($0,4,?)=vorname;}
(FILENAME!="-"){if(substr($0,4?)==AU1)print vorname, substr($0,4?)}
' 2_liste > Ergebnis.txt Hat jemand eine Idee zu dem Problem? Danke Nobody0815
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11179
Wohnort: München
|
Was ist denn in deinem Beispiel mit Jonas Schneider? Der fällt da irgendwie unter den Tisch... Mit ohne awk würde ich das so machen (benötigt Python >= 3.8 wegen dem Walrus-Operator (:= ):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 | #!/usr/bin/env python3
import re
import sys
entry_re = re.compile(r"(?P<index>\D+\d+)(?P<name>\D+)(?P<other>\d+)")
if len(sys.argv) != 3:
print(f"usage: {sys.argv[0]} FILE1 FILE2", file=sys.stderr)
exit(1)
with open(sys.argv[1]) as f1, open(sys.argv[2]) as f2:
last_names = {}
for line in f1:
if (match := entry_re.match(line)):
last_names[match.group('index')] = match.group('name')
first_names = {}
for line in f2:
if (match := entry_re.match(line.strip())):
first_names[match.group('index')] = match.group('name')
for key, name in sorted(last_names.items()):
if (first_name := first_names.get(key)):
print(first_name, name)
|
$ ./group_names.py 1_liste 2_liste
Anna Krueger
Emilia Schulze
Leonie Meier
Paul Mueller
Liam Weber
Jonas Schneider
|
Nobody0815
(Themenstarter)
Anmeldungsdatum: 29. April 2020
Beiträge: 12
|
Hallo seahawk1986, danke für die Überlegung mit Python. 'Jonas Schneider' habe ich in meiner Ergebisliste schlicht vergessen. Sorry 😕 Leider steht mir, auf dem Zielsystem, nur Python in der Version 2.7.17 zur Verfügung. Daran kann ich auch leider nichts ändern. Kannst Du mir trotzdem die nachfolgende Zeile aus Deinem Skript erklären (Python ist noch Neuland für mich)? | try_re = re.compile(r"(?P<index>\D+\d+)(?P<name>\D+)(?P<other>\d+)")
|
Danke und VG
Nobody0815
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11179
Wohnort: München
|
Nobody0815 schrieb: Leider steht mir, auf dem Zielsystem, nur Python in der Version 2.7.17 zur Verfügung. Daran kann ich auch leider nichts ändern.
Dann ist es etwas unsinnig Ubuntu 20.04 als Systemversion anzugeben, bei dem mit großen Aufwand Python2 aus den Kernpaketen eliminiert und nach universe verschoben wurde 😇 Man kann das natürlich auch mit Python2 machen:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 | #!/usr/bin/env python2
# -*- coding: utf-8 -*-
import codecs
import re
import sys
entry_re = re.compile(r"(?P<index>\D+\d+)(?P<name>\D+)(?P<other>\d+)")
if len(sys.argv) != 3:
print "usage: {} FILE1 FILE2".format(sys.argv[0]) >> sys.stderr
exit(1)
with codecs.open(sys.argv[1], encoding='utf-8') as f1:
last_names = {}
for line in f1:
match = entry_re.match(line)
if match:
last_names[match.group('index')] = match.group('name')
with codecs.open(sys.argv[2], encoding='utf-8') as f2:
first_names = {}
for line in f2:
match = entry_re.match(line.strip())
if match:
first_names[match.group('index')] = match.group('name')
for key, name in sorted(last_names.items()):
first_name = first_names.get(key)
if first_name:
print first_name, name
|
Kannst Du mir trotzdem die nachfolgende Zeile aus Deinem Skript erklären (Python ist noch Neuland für mich)? | try_re = re.compile(r"(?P<index>\D+\d+)(?P<name>\D+)(?P<other>\d+)")
|
Das kompiliert ein Objekt für einen regulären Ausdruck aus mit drei benannten Gruppen (in https://docs.python.org/2.7/library/re.html nach (?P<name>...) suchen).
Das r vor dem String sorgt dafür, dass Backslashes nicht als Escape-Sequenzen interpretiert werden.
Ich matche also für die Gruppe index auf mindestens ein Zeichen, das keine Zahl ist \D+ , gefolgt von mindestens einer Ziffer \d+ . Die Zweite Gruppe name besteht aus mindestens einem Zeichen, das keine Ziffer ist und die dritte Gruppe other wiederum aus mindestens einer Ziffer.
|
Nobody0815
(Themenstarter)
Anmeldungsdatum: 29. April 2020
Beiträge: 12
|
Hallo, @seahawk1986: Danke für die Mühe, aber meine Pythonkenntnisse sind leider nicht so besonders. Ich schaue mir Dein Skript bei Gelegenheit genauer an, brauche aber zeitnah eine Lösung. Ich habe mein AWK-Script überarbeitet (siehe unten), dass Ergebnis geht in die richtige Richtung, ist aber noch nicht wirklich gut.
Für den Augenblick reicht es mir, wenn die "AU-Nummern" am Anfang einer jeden Zeile genutzt werden und damit die beiden Listen zusammengeführt werden. Die Zahlenkolonnen nach den Namen können erstmal ignoriert werden. Mein aktueller Code: cat 2_liste | awk '
(FILENAME=="-"){AUvorname=substr($0,1,5); vorname=substr($0,6);}
(FILENAME!="-"){AUname=substr($0,1,5); name=substr($0,6);}
{if(AUvorname == AUname) print vorname, name;}
' - 1_liste >> ERGEBNIS Ergebnis der Ausführung: Paul49494 Mueller0013848 Positiv: Vorname und Name ist korrekt zusammengeführt. Negativ: Wo sind die restlichen Namen? Vermutlich überschreibe ich mir die Vornamen und Namen. Der Vorname 'Paul' steht ganz unten in '2_liste'.
Der Grund für das Ergebnis ist, dass AWK die Listen nacheinander abarbeitet. Zuerst wird die Liste '2_liste', welche mit 'cat |' übergeben wurde, Zeile für Zeile abgearbeitet. Die Variable 'AUvorname' wird dabei immer wieder überschrieben. Der letzte Eintrag der Liste 'AU004' (vgl. Paul) bleibt zum Schluss in der Variable 'AUvorname' stehen.
Jetzt wird die nächste Liste '1_liste' Zeile für Zeile abgearbeitet. An der Stelle, wo die IF-Bedingung greift (Variable 'AUvorname' = 'AUname') wird ein Eintrag in 'ERGEBNIS' erzeugt und das ist jetzt genau einmal der Fall. 😕 Das Problem verursacht die sequenzielle Abarbeitung der beiden Listen. 1. Lösungsansatz: Die Variablen 'AUvorname' und 'AUname' müssen Zeile für Zeile verglichen werden. Dazu müsste AWK ständig zwischen den beiden Listen wechseln. Geht das überhaupt? 2. Lösungsansatz: Die Variable 'AUvorname' muss in ein Array (oder etwas ähnliches), damit alle AUvorname-Einträge gespeichert werden und nicht nur der Letzte. Wie erzeugt man mit awk ein Array und spricht diese hinterher wieder an? 🙄 Erklärung zu FILENAME: In FILENAME ist der Name der Datei gespeichert, die gearde abgearbeitet wird: (FILENAME=="-") –> 2_liste –> Solange FILENAME = 2_liste mache irgendwas. (FILENAME!="-") –> 2_liste –> Wenn FILENAME nicht mehr = 2_liste mache irgendwas anderes.
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11179
Wohnort: München
|
Hier mal ein awk-Skript, das die Dateien mit den Vor- und Nachnamen als Argumente nimmt und das selbe Ergebnis wie das Python-Skript liefert:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | #!/usr/bin/awk -f
(ARGIND==1) {
s=substr($0,6)
sub(/[0-9]+/,"", s)
first_names[substr($0,1,5)]=s
}
(ARGIND==2) {
s=substr($0,6)
sub(/[0-9]+/,"", s)
last_names[substr($0,1,5)]=s
}
END {
asorti(last_names, sorted_last_names_indices)
for (i in sorted_last_names_indices) {
id = sorted_last_names_indices[i]
if (id in first_names) {
print last_names[id] " " first_names[id]
}
}
}
|
$ ./test.awk 1_liste 2_liste
Anna Krueger
Emilia Schulze
Leonie Meier
Paul Mueller
Liam Weber
Jonas Schneider
|
Nobody0815
(Themenstarter)
Anmeldungsdatum: 29. April 2020
Beiträge: 12
|
Hallo seahawk1986, funktioniert super! 💡 Jetzt muss ich noch im www nachlesen, wie ARGIND und asorti genau funktionieren. 😎 Danke für die Lösung des Problems. 👍 VG Nobody0815 seahawk1986 schrieb: Hier mal ein awk-Skript, das die Dateien mit den Vor- und Nachnamen als Argumente nimmt und das selbe Ergebnis wie das Python-Skript liefert:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | #!/usr/bin/awk -f
(ARGIND==1) {
s=substr($0,6)
sub(/[0-9]+/,"", s)
first_names[substr($0,1,5)]=s
}
(ARGIND==2) {
s=substr($0,6)
sub(/[0-9]+/,"", s)
last_names[substr($0,1,5)]=s
}
END {
asorti(last_names, sorted_last_names_indices)
for (i in sorted_last_names_indices) {
id = sorted_last_names_indices[i]
if (id in first_names) {
print last_names[id] " " first_names[id]
}
}
}
|
$ ./test.awk 1_liste 2_liste
Anna Krueger
Emilia Schulze
Leonie Meier
Paul Mueller
Liam Weber
Jonas Schneider
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12822
|
seahawk1986 schrieb:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | #!/usr/bin/awk -f
(ARGIND==1) {
s=substr($0,6)
sub(/[0-9]+/,"", s)
first_names[substr($0,1,5)]=s
}
(ARGIND==2) {
s=substr($0,6)
sub(/[0-9]+/,"", s)
last_names[substr($0,1,5)]=s
}
END {
asorti(last_names, sorted_last_names_indices)
for (i in sorted_last_names_indices) {
id = sorted_last_names_indices[i]
if (id in first_names) {
print last_names[id] " " first_names[id]
}
}
}
|
Warum baust Du da runde Klammern um die Kriterien? Die sind jedenfalls überflüssig.
|
snafu1
Anmeldungsdatum: 5. September 2007
Beiträge: 2123
Wohnort: Gelsenkirchen
|
Mit geschicktem Pattern-Matching ist das auch als (etwas längerer) Einzeiler machbar. Zunächst einmal die Trennung zwischen ID und dem Namen. Dabei gehe ich von 5 beliebigen Zeichen am Anfang aus, welche die ID repräsentieren. Dann folgt eine beliebige Anzahl von Groß- und Kleinbuchstaben. Am Ende kommt "etwas anders" (.*), nämlich diese Zahlen. Interessant sind aber nur die ersten beiden Dinge, weshalb ich hierfür Klammern nutze, um sie später als Gruppe ansprechen zu können. match() speichert das Ergebnis als Array ins letzte Argument (hier: m). Das erste Argument ist der zu durchsuchende Text (hier: komplette Zeile) und das zweite ist mein Pattern. Im Folgenden einfach mal stumpf die Ausgabe der Inhalte von der ersten und zweiten Gruppe:
$ awk 'match($0, /(.{5})([A-Za-z]+).*/, m){ print m[1], m[2] }' vornamen.txt nachnamen.txt
AU002 Emilia
AU005 Liam
AU001 Anna
AU006 Jonas
AU003 Leonie
AU004 Paul
AU001 Krueger
AU003 Meier
AU002 Schulze
AU004 Mueller
AU006 Schneider
AU005 Weber
AU007 Schmidt Und nun als Erweiterung das Merken der Namen über beide Dateien hinweg, sowie das abschließende Zusammenführen. ARGIND ist die Nummer der aktuellen Datei. Die Eins steht halt fürs erste Argument (hier: vornamen.txt) und die Zwei entsprechend für die Nachnamen. Ich lege nun also die ID und den Namen ins names-Array ab und trenne das nach dem jeweiligen ARGIND. Und am Ende, wenn alle Zeilen durch sind, werden halt meine Ergebnisse durchlaufen und zusammengeführt. Hier das Ganze in Code: $ awk 'match($0, /(.{5})([A-Za-z]+).*/, m){ names[ARGIND][m[1]] = m[2] } END{ for (id in names[1]) print names[1][id], names[2][id] }' vornamen.txt nachnamen.txt
Leonie Meier
Paul Mueller
Liam Weber
Jonas Schneider
Anna Krueger
Emilia Schulze Ich hoffe, die Erklärung war einigermaßen anfängerfreundlich. 😉
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11179
Wohnort: München
|
rklm schrieb: Warum baust Du da runde Klammern um die Kriterien? Die sind jedenfalls überflüssig.
Genauso wie der Whitespace und sprechende Variablennamen... - ich finde das visuell übersichtlicher, aber ich mache normalerweise kaum etwas mit awk, weil mir das ohne Typ-Prüfung und implizitem verschlucken von Fehlern nur bei "perfekten" Daten sinnvoll erscheint. snafu1 schrieb: Mit geschicktem Pattern-Matching ist das auch als (etwas längerer) Einzeiler machbar.
Interessant, dann kann awk also auch Gruppen in regulären Ausdrücken nutzen. Der Ansatz sortiert aber noch nicht die Ergebnisse nach der ID. Zumindest mit gawk (Version GNU Awk 5.1.0, API: 3.0 (GNU MPFR 4.0.2, GNU MP 6.2.0)) schaut er auch nicht, ob es tatsächlich einen Vornamen zum Nachnamen gibt, da taucht Schmidt alleine in der Ausgabe auf (ich sehe da auch nichts im Code, das die Prüfung machen würde, ob es einen Eintrag im Assoziativen Array gibt):
$ awk 'match($0, /(.{5})([A-Za-z]+).*/, m){ names[ARGIND][m[1]] = m[2] } END{ for (id in names[1]) print names[1][id], names[2][id] }' 1_liste 2_liste
Meier Leonie
Mueller Paul
Weber Liam
Schneider Jonas
Schmidt
Krueger Anna
Schulze Emilia
|
Nobody0815
(Themenstarter)
Anmeldungsdatum: 29. April 2020
Beiträge: 12
|
Hallo snafu1, danke für die super Erklärung. 👍 Hat der Anfänger auf dieser Seite des Bildschirms grundsätzlich verstanden. ☺ VG Nobody0815
|
snafu1
Anmeldungsdatum: 5. September 2007
Beiträge: 2123
Wohnort: Gelsenkirchen
|
Hier mal ein Ansatz mit sed. Die Sortierung führe ich vorab durch, da jede Zeile durch ihre vorangestellte ID ja schon sortierbar ist. Mit dem paste-Kommando fügt er die beiden Dateien zeilenweise zu jeweils einer Zeile zusammen (getrennt durch einen Tabulator). Die Umleitungen sind an der Stelle nötig, damit ich die getrennten Datenströme von der Sortierung als Argumente ("Pseudo-Dateien") übergeben kann. Auch zur Übergabe an sed habe ich eine Umleitung benutzt, weil ich dann die Dateinamen am Ende stehen hab - könnte man aber auch mit Pipes machen und entsprechend nach vorne stellen. Bei sed nutze ich nun das schon bekannte Pattern, aber quasi doppelt (getrennt durch den Tabulator). Die IDs sind mir hierbei egal, weil ich schon die gewünschte Sortierung habe. Meine Gruppen 1 und 2 sind an der Stelle also Vor- und Nachname. Sieht dann am Ende so bei mir aus:
$ sed -rn 's/.{5}([A-Za-z]+).*\t.{5}([A-Za-z]+).*/\1 \2/p' <(paste <(sort vornamen.txt) <(sort nachnamen.txt))
Anna Krueger
Emilia Schulze
Leonie Meier
Paul Mueller
Liam Weber
Jonas Schneider
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11179
Wohnort: München
|
Das funktioniert nur zufällig, weil Schmidt nach dem Sortieren der Dateien am Ende steht. Wenn so eine Lücke in den Daten bei einem Eintrag davor auftreten kann, wird es zu einer Leserasterverschiebung mit falschen Paaren für die nachfolgenden Einträge kommen.
|
snafu1
Anmeldungsdatum: 5. September 2007
Beiträge: 2123
Wohnort: Gelsenkirchen
|
Dann nochmal mit Unterstützung von awk:
$ awk 'match($0, /(.{5})([A-Za-z]+).*/, m) {if (m[1]==id) {print name, m[2]} id=m[1]; name=m[2]}' <(paste -d'\n' <(sort vornamen.txt) <(sort nachnamen.txt))
Anna Krueger
Emilia Schulze
Leonie Meier
Paul Mueller
Liam Weber
Jonas Schneider
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11179
Wohnort: München
|
Das klappt bei mehreren Lücken in den Indices nicht - z.B.: vornamen.txt
| AU001Simpson4358309
AU004Simpson4358309
AU006Simpson4358309
AU017Burns23489
AU010Smithers439508
AU014Quimby3048998
|
nachnamen.txt
| AU001Homer4358394
AU003Bar3248932
AU005Marge345893748
AU004Lisa384723894
AU006Maggie430598
AU009Grandpa434390
AU017Charles4958
AU014Joe439584
|
awk 'match($0, /(.{5})([A-Za-z]+).*/, m) {if (m[1]==id) {print name, m[2]} id=m[1]; name=m[2]}' <(paste -d'\n' <(sort vornamen.txt) <(sort nachnamen.txt))
Homer Simpson
Simpson Lisa
In dem Fall kommt er mit Vor- und Nachnamen durcheinander und gibt nicht alle Matches aus, die man erwarten würde...
Homer Simpson
Lisa Simpson
Maggie Simpson
Joe Quimby
Charles Burns
|