Keiki
Anmeldungsdatum: 20. November 2014
Beiträge: 46
|
Hallo, ich benötige ein Skript, mit dem ich eine strukturierte Textdatei in eine Tabelle überführen kann. Gegeben ist die Textdatei ALT.txt mit diesem Aufbau: 'Personalnummer
(leer)
Name:
Vorname:
Abteilung:
Archiv: /pfad - Dateigröße
(leer)
(leer) Hierzu ein Beispiel mit zwei Einträgen:
'0246
Name: Einer
Vorname: Erwin
Abteilung: Einkauf
Archiv: /pfad/E/eineerwi -- 3.21 GB
'00742
Name: Kanns
Vorname: Karl
Abteilung: Küche
Archiv: /pfad/K/kannkarl -- 1.23 GB Zu erstellen ist die Textdatei NEU.txt mit dieser Struktur:
ID Personalnummer Abteilung Dateigröße Auf mein Beispiel bezogen, sollte die neue Textdatei so aussehen:
001 0246 Einkauf 3.21
002 00742 Küche 1.23 Die ID und die Personalnummer sollen mit führenden Nullen ausgegeben werden. Mit grep und sed komme ich nicht wirklich weiter. Zwar liefert ein
grep -e '^Abteilung:\s' ALT.txt | sed -e 's/^Abteilung:\s//'
eine Liste mit alle Abteilungen, aber das allein bringt nicht weiter. Viele Grüße Keiki
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12822
|
Keiki schrieb:
Mit grep und sed komme ich nicht wirklich weiter.
Für die Aufgabe ist auch viel besser awk geeignet.
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11179
Wohnort: München
|
Oder mit Python3:
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
31
32
33
34
35
36
37
38 | #!/usr/bin/env python3
from itertools import cycle
fields = ['Personalnummer',
None,
None,
None,
'Abteilung',
'Archiv',
None,
None
]
g = cycle(fields)
current_field = None
current_elements = {}
id = 0
with open('ALT.txt',encoding="utf-8") as f:
for line in f.readlines():
line = line.strip()
current_field = next(g)
if current_field:
if current_field == "Personalnummer":
line = line[1:]
elif current_field == "Abteilung":
line = line.split(":")[-1].strip()
elif current_field == "Archiv":
line = line.split()[-2]
current_elements[current_field] = line
if len(current_elements) >= 3:
id += 1
print("{:03d}\t{}\t{}\t{}".format(
id,
current_elements['Personalnummer'],
current_elements['Abteilung'],
current_elements['Archiv'])
)
current_elements = {}
|
Liefert dann mit dem Beispieldatensatz: $ python3 transform.py
001 0246 Einkauf 3.21
002 00742 Küche 1.23
|
linuxer-nds
Anmeldungsdatum: 1. Februar 2015
Beiträge: 33
|
Hallo Keiki,
mein Vorschlag wäre folgendes Bash Skript:
1
2
3
4
5
6
7
8
9
10
11
12
13 | #!/bin/bash
spc=" "
counter=1
cat ALT.txt | while read line;
do
echo $line | grep -q "^'" && persnr=$(echo $line | sed -e 's/^.0*//');
echo $line | grep -q '^Abteilung' && abtlg="$(echo $line | sed -e 's/^Abteilung: *//')$spc";
echo $line | grep -q '^Archiv' && { size="$(echo $line | sed -e 's/.*-- *//')";
printf "%05d %06d " $counter $persnr;
echo "${abtlg:0:10} $size";
counter=$[counter+1]; };
done
|
Die exakt formatierte Ausgabe von Strings mit Umlauten benötigte hier etwas Trickserei, da printf diese nicht mag, Stichwort multibyte chars, siehe Skript Zeilen 3, 8 und 11. MfG linuxer-nds
|
Keiki
(Themenstarter)
Anmeldungsdatum: 20. November 2014
Beiträge: 46
|
Zunächst vielen Dank für eure Unterstützung! @rklm: An einer Lösung mit awk bin ich sehr interessiert. Bislang scheiterten alle meine Versuche, mehrzeilige Datensätze zu verarbeiten. Ich habe noch nicht verstanden, welche Werte ich für den Record Selector (RS) und den Field Selector (FS) festlegen muss. In meinem Beispiel müssten folgende Regeln gelten: Ein gültiger Datensatz beginnt ein Hochkomma am Zeilenanfang, also RS="^'" . Die dazugehörigen Felder sind mit einem Zeilenumbruch voneinander getrennt, also FS="\n" .
Mit den genannten Wertzuweisungen für RS und FS gelingt es mir nicht, ungültige Datensätze zu überspringen (z. B. mehrzeilige Kommentare am Anfang der Datei ALT.txt). Davon abgesehen schaffe ich es auch nicht bei einer bereinigten Ausgangsdatei ohne Kommentare, die zu einem Datensatz gehörigen Felder mit $1, $2, ... direkt anzusprechen. @seahawk1986: Respekt! Deine Lösung funktioniert perfekt und ist erstaunlich performant. Ich werde mich wohl weiter in Python einarbeiten müssen, damit ich deine Lösung besser nachvollziehen kann und in ferner Zukunft selbst in der Lage bin, so eine Lösung herzuzaubern. @linuxer-nds: Deine Lösung kommt meinen bisherigen Experimenten am nächsten, mit dem erfreulichen Unterschied, dass dein Ansatz funktioniert. Bitte erlaube mir noch eine Frage, die vom gegebenen Beispiel abweicht: Beim Herumprobieren ist mir aufgefallen, dass die Extraktion der Personalnummer nicht funktioniert, wenn statt eines Hochkommas eine Leertaste oder ein Tabulator vorangestellt ist. Demzufolge misslingt: echo $line | grep -q "^\s" && persnr=$(echo $line | sed -e 's/^.0*//'); Die verwendete grep -sed -Kombination filtert allerdings richtig:
cat ALT.txt | grep "^\s" | sed -e 's/^.0*//' Viele Grüße Keiki
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12822
|
Keiki schrieb:
An einer Lösung mit awk bin ich sehr interessiert. Bislang scheiterten alle meine Versuche, mehrzeilige Datensätze zu verarbeiten. Ich habe noch nicht verstanden, welche Werte ich für den Record Selector (RS) und den Field Selector (FS) festlegen muss.
Das kann man mit den Selektoren vermutlich nicht regeln. Ich würde das eher so machen: Dokument zeilenweise lesen und das Feld in jeder Zeile in einem assoziativen Array speichern. Beim nächsten Datensatz oder im END-Block verarbeitest Du ihn dann.
In meinem Beispiel müssten folgende Regeln gelten: Ein gültiger Datensatz beginnt ein Hochkomma am Zeilenanfang, also RS="^'" . Die dazugehörigen Felder sind mit einem Zeilenumbruch voneinander getrennt, also FS="\n" .
Das kann man doch schön übersetzen: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 | $ awk -f data.awk data.txt
0246 Einer
00742 Kanns
$ cat data.awk
/^'[0-9]+/ {
if (record["id"]) {
print record["id"] " " record["name"]
}
delete record;
if (match($0, /^'([0-9]+)/, m)) {
record["id"] = m[1]
}
}
/^Name: / { record["name"] = $2 }
/^Vorname: / { record["vorname"] = $2 }
END {
if (record["id"]) {
print record["id"] " " record["name"]
}
}
|
Mit den genannten Wertzuweisungen für RS und FS gelingt es mir nicht, ungültige Datensätze zu überspringen (z. B. mehrzeilige Kommentare am Anfang der Datei ALT.txt). Davon abgesehen schaffe ich es auch nicht bei einer bereinigten Ausgangsdatei ohne Kommentare, die zu einem Datensatz gehörigen Felder mit $1, $2, ... direkt anzusprechen.
Das ist eine Folge Deines Versuches, das mit den Feld- und Datensatzselektoren zu regeln. ☺
@seahawk1986: Respekt! Deine Lösung funktioniert perfekt und ist erstaunlich performant. Ich werde mich wohl weiter in Python einarbeiten müssen, damit ich deine Lösung besser nachvollziehen kann und in ferner Zukunft selbst in der Lage bin, so eine Lösung herzuzaubern.
Wenn ich den Python-Code richtig verstehe, ist der sehr strikt, was die Reihenfolge der Felder angeht, sprich: es wird erwartet, dass die Felder immer alle und in genau definierter Reihenfolge kommen. Ich würde da immer die Feldnamen aus der Eingabe parsen, zumal Du ja auch ungültige Datensätze erwartest. So etwas geht mit Ruby auch schön: 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 | #!/usr/bin/ruby
def maybe_output(record)
printf("%s %s\n", record[:id], record[:name]) if record
end
record = nil
ARGF.each_line do |line|
case line
when /^'(\d+)/
# new record!
maybe_output(record)
record = {id: $1}
when /^Name:\s*(.*)$/
record[:name] = $1
# more fields here...
when /^\s*(?:#.*)?$/
# empty lines or comments silently ignored
else
warn "WARNING: ignoring line: #{line}"
end
end
maybe_output(record)
|
Man sollte aber Reguläre Ausdrücke mögen. 😉
@linuxer-nds: Deine Lösung kommt meinen bisherigen Experimenten am nächsten, mit dem erfreulichen Unterschied, dass dein Ansatz funktioniert.
Da gibt es aber Probleme mit dem Quoting, z.B. von $line. Ich würde so etwas nicht mehr mit der Shell machen. Mindestens awk sollte es sein, weil man sich sonst einfach nur unnötig einen abbricht. Ciao robert
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11179
Wohnort: München
|
rklm schrieb: @seahawk1986: Respekt! Deine Lösung funktioniert perfekt und ist erstaunlich performant. Ich werde mich wohl weiter in Python einarbeiten müssen, damit ich deine Lösung besser nachvollziehen kann und in ferner Zukunft selbst in der Lage bin, so eine Lösung herzuzaubern.
Wenn ich den Python-Code richtig verstehe, ist der sehr strikt, was die Reihenfolge der Felder angeht, sprich: es wird erwartet, dass die Felder immer alle und in genau definierter Reihenfolge kommen. Ich würde da immer die Feldnamen aus der Eingabe parsen, zumal Du ja auch ungültige Datensätze erwartest.
Genau, das Skript nimmt immer die Zeilen 1, 5 und 6 und ignoriert den Rest des 8 Zeilen langen Blocks für einen Mitarbeiter. Damit funktioniert es natürlich nur, wenn die im ersten Post angegebene Reihenfolge fest eingehalten wird, hat aber den Vorteil, dass man sich nicht jede einzelne Zeile mit einem oder mehreren regulären Ausdrücken ansehen muss. Ob das Sinn macht, hängt natürlich von der Qualität der vorhandenen Daten ab.
|
linuxer-nds
Anmeldungsdatum: 1. Februar 2015
Beiträge: 33
|
Hallo Keiki,
Beim Herumprobieren ist mir aufgefallen, dass die Extraktion der Personalnummer nicht funktioniert, wenn statt eines Hochkommas eine Leertaste oder ein Tabulator vorangestellt ist.
am besten, man filtert die Textdatei vor der Auswertung, zuerst mal führende Tabs, Spaces und Hochkommas entfernen:
| cat ALT.txt | sed -e "s/^[\t ']*//" | while read line;
|
Damit verändert sich Zeile 7 zu:
| echo $line | grep -q "^[0-9]" && persnr=$(echo $line | sed -e 's/^0*//');
|
MfG linuxer-nds
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12822
|
linuxer-nds schrieb:
Damit verändert sich Zeile 7 zu:
| echo $line | grep -q "^[0-9]" && persnr=$(echo $line | sed -e 's/^0*//');
|
Was immer noch ein Quoting- und ein Effizienzproblem hat. Ist m.E. das falsche Werkzeug, das hier gewählt wurde. Aber ich wiederhole mich.
|
linuxer-nds
Anmeldungsdatum: 1. Februar 2015
Beiträge: 33
|
rklm schrieb Was immer noch ein Quoting- und ein Effizienzproblem hat.
Mag sein, aber ich wollte nur eine Frage beantworten.
Wobei ich jetzt erst die eigentliche Frage verstehe: Beim Herumprobieren ist mir aufgefallen, dass die Extraktion der Personalnummer nicht funktioniert, wenn statt eines Hochkommas eine Leertaste oder ein Tabulator vorangestellt ist.
Bei der Variable "line" entfernt die Bash automatisch führende Spaces bzw. Tabs.
D.h. entweder beginnt "line" mit einem Hochkomma oder direkt mit einer Zahl.
Insofern musst du im Skript nur Zeile 7 ändern zu
| echo $line | sed -e "s/^'//" | grep -q "^[0-9]" && persnr=$(echo $line | sed -e 's/^0*//');
|
MfG linuxer-nds
|
ExcitedSpoon
Anmeldungsdatum: 17. Juli 2010
Beiträge: 226
Wohnort: /home/berlin
|
linuxer-nds schrieb: Bei der Variable "line" entfernt die Bash automatisch führende Spaces bzw. Tabs.
D.h. entweder beginnt "line" mit einem Hochkomma oder direkt mit einer Zahl.
Insofern musst du im Skript nur Zeile 7 ändern zu
| echo $line | sed -e "s/^'//" | grep -q "^[0-9]" && persnr=$(echo $line | sed -e 's/^0*//');
|
Das löst aber immernoch nicht das vom rklm zurecht bemängelte Quoting-Problem.
sed und grep sind hier auch nicht gerade die besten tools für diese Aufgabe. Das ist nur unnötiges Gefrickel. awk, python oder ruby wäre hier auch meine erste Wahl gewesen. Grüße
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12822
|
ExcitedSpoon schrieb: linuxer-nds schrieb:
| echo $line | sed -e "s/^'//" | grep -q "^[0-9]" && persnr=$(echo $line | sed -e 's/^0*//');
|
sed und grep sind hier auch nicht gerade die besten tools für diese Aufgabe. Das ist nur unnötiges Gefrickel. awk, python oder ruby wäre hier auch meine erste Wahl gewesen.
Selbst mit sed geht die Zeile oben einfacher: | persnr="$(echo "$line" | sed -ne "s/^'0*//p")"
|
☺ seahawk1986 schrieb: rklm schrieb:
Wenn ich den Python-Code richtig verstehe, ist der sehr strikt, was die Reihenfolge der Felder angeht, sprich: es wird erwartet, dass die Felder immer alle und in genau definierter Reihenfolge kommen. Ich würde da immer die Feldnamen aus der Eingabe parsen, zumal Du ja auch ungültige Datensätze erwartest.
Genau, das Skript nimmt immer die Zeilen 1, 5 und 6 und ignoriert den Rest des 8 Zeilen langen Blocks für einen Mitarbeiter. Damit funktioniert es natürlich nur, wenn die im ersten Post angegebene Reihenfolge fest eingehalten wird, hat aber den Vorteil, dass man sich nicht jede einzelne Zeile mit einem oder mehreren regulären Ausdrücken ansehen muss.
Nun ja, man kann das auch mit weniger Regulären Ausdrücken machen, indem man einfach die Strings vor dem Doppelpunkt als Feldnamen benutzt: 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/ruby
def maybe_output(record)
printf("%s %s\n", record[:id], record['Name']) if record
end
record = nil
ARGF.each_line do |line|
case line
when /^'(\d+)/
maybe_output(record)
record = {id: $1}
when /^(\w+):\s*(.*)$/
# all fields
record[$1] = $2
when /^\s*(?:#.*)?$/
# empty lines or comments silently ignored
else
warn "WARNING: ignoring line: #{line}"
end
end
maybe_output(record)
|
Ob das Sinn macht, hängt natürlich von der Qualität der vorhandenen Daten ab.
Genau. Viele Grüße robert
|
Keiki
(Themenstarter)
Anmeldungsdatum: 20. November 2014
Beiträge: 46
|
Eure Antworten haben mir sehr weitergeholfen - herzlichen Dank dafür! Ansonsten habe ich noch gespeichert, dass AWK, Python oder Ruby die geeignetere Werkzeug sind für Vorhaben wie diese. Gibt es Gründe, warum Perl nicht genannt wurde? Viele Grüße Keiki
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11179
Wohnort: München
|
Keiki schrieb: Gibt es Gründe, warum Perl nicht genannt wurde?
Man kann das sicherlich auch in Perl umsetzen (wie auch in jeder beliebigen Turing-vollständigen Sprache).
Wenn man Perl beherrscht, spricht nichts dagegen es nutzen - wenn nicht kann man sich überlegen stattdessen eine schönere Sprache zu lernen 😉
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12822
|
Keiki schrieb: Eure Antworten haben mir sehr weitergeholfen - herzlichen Dank dafür!
Fein. Bitte!
Ansonsten habe ich noch gespeichert, dass AWK, Python oder Ruby die geeignetere Werkzeug sind für Vorhaben wie diese.
Gut!
Gibt es Gründe, warum Perl nicht genannt wurde?
M.E. gibt es nur zwei Dinge, die für Perl sprechen: http://www.cpan.org/ Es ist auf vielen Systemen installiert.
Es ist i.d.R. etwas schneller in der Ausführung als die Kollegen Python und Ruby, aber das ist bei heutigen Zeiten kein nennenswerter Vorteil mehr.
|