michahe
Anmeldungsdatum: 12. Dezember 2013
Beiträge: 824
|
Hallo,
ich möchte in TXT-Dateien die (sortierten) Zeilen am Trennzeichen "|" teilen, die Teilstrings vergleichen und Dubletten oder Fehler in eine neue Datei ausgeben. Herausforderung: Es können zwei oder drei Teilstrings vorhanden sein. Leerzeichen um das Trennzeichen muss ignoriert werden. Bedingungen für Fehler oder Dublette: Es sind weniger als zwei Teilstrings vorhanden. Es sind mehr als drei Teilstrings vorhanden. Wenn die Zeile zwei Teilstrings hat: Teilstring 1 taucht noch einmal auf. Wenn die Zeile drei Teilstrings hat: Die Verkettung von Teilstring 1 und Teilstring 2 taucht noch einmal auf.
Hier ein Beispiel Text.TXT: AAA | # Fehler 1: Der zweite Teilstring ist LEER; Zeile ausgeben
AAA | BBB # Fehler 3: beide Zeilen ausgeben
AAA | BBB
AAA | CCC # kein Fehler
AAA | DDD | EEE # Fehler 4: beide Zeilen ausgeben
AAA | DDD | EEE
AAA | DDD | EEE | FFF # Fehler 2, Zeile ausgeben
Wie fange ich an? Bitte (wegen anderer bereits existirerender Code-Teile) nur BASH. Danke, Michael
|
wxpte
Anmeldungsdatum: 20. Januar 2007
Beiträge: 1179
Wohnort: Schäl Sick
|
Vielleicht machst du hier auch einen Denkfehler, die Beschreibung der Fehler und die Kommentare in der Text.TXT widersprechen sich nämlich in einem Punkt:
3. Wenn die Zeile zwei Teilstrings hat: Teilstring 1 taucht noch einmal auf
Dagegen:
AAA | CCC # kein Fehler Eigentlich müsste das auch unter Fehler Nr. 3 fallen, denn der Teilstring
AAA
taucht ja auch in der Kombination
AAA | BBB schon auf. Mir scheint es eher, als würdest du für die Dubletten gar keine Aufteilung in Teilstrings benötigen, sondern willst nur zusätzlich prüfen, ob weniger als zwei, bzw. mehr als drei Teilstrings vorhanden sind. Ergänzung: Was mir noch aufgefallen ist: in Zeile 1 der Text.TXT taucht der Feldtrenner als Abschluss der Zeile auf, in den übrigen Zeilen dagegen nicht. Für eine sinnvolle Analyse müsste das schon einheitlich sein.
|
michahe
(Themenstarter)
Anmeldungsdatum: 12. Dezember 2013
Beiträge: 824
|
Danke wxpte, ich muss die Bedingungen ergänzen: Es können zwei oder drei Teilstrings vorhanden sein. Leerzeichen um das vor und nach dem Trennzeichen müssen ignoriert werden. Bedingungen für Fehler oder Dublette:
Ein Teilstring darf nicht leer sein, d.h. nach dem Trennzeichen muss mindestens ein Zeichen folgen, das nicht BLANK ist,
Es sind weniger als zwei Teilstrings vorhanden. Es sind mehr als drei Teilstrings vorhanden.
b. Es werden Zeilen nur mit identischer Anzahl Teilstrings (entweder beide haben zwei ODER beide haben drei Stück) verglichen. Wenn die Zeile zwei Teilstrings hat: Teilstring 1 taucht noch einmal auf. Wenn die Zeile drei Teilstrings hat: Die Verkettung von Teilstring 1 und Teilstring 2 taucht noch einmal auf.
|
wxpte
Anmeldungsdatum: 20. Januar 2007
Beiträge: 1179
Wohnort: Schäl Sick
|
Vorab ein paar allgemeine Tipps. Hierzu habe ich die Kommentare aus der Text.txt entfernt, die Leerzeichen aber stehen lassen:
cat Text.txt | tr ' ' '.'
AAA.|...................
AAA.|.BBB...............
AAA.|.BBB
AAA.|.CCC...............
AAA.|.DDD.|.EEE.........
AAA.|.DDD.|.EEE
AAA.|.DDD.|.EEE.|.FFF...
Wenn du nun die Dubletten finden willst, brauchst du eigentlich nicht jede Zeile einzeln auswerfen, sondern es reicht der Befehl
cat Text.txt | tr -d ' ' | uniq -cd
2 AAA|BBB
2 AAA|DDD|EEE
Dann siehst du die jeweilige Zeile ohne Leerzeichen (die wolltest du ja ignorieren) und die Häufigkeit, mit der die Zeile vorkommt. Ansonsten empfehle ich, das Beispiel mit der while-read Schleife anzusehen: ich denke, die Verwendung von Arrays ist das richtige Werkzeug für dein Vorhaben.
|
wxpte
Anmeldungsdatum: 20. Januar 2007
Beiträge: 1179
Wohnort: Schäl Sick
|
michahe schrieb: b. Es werden Zeilen nur mit identischer Anzahl Teilstrings (entweder beide haben zwei ODER beide haben drei Stück) verglichen. Wenn die Zeile zwei Teilstrings hat: Teilstring 1 taucht noch einmal auf. Wenn die Zeile drei Teilstrings hat: Die Verkettung von Teilstring 1 und Teilstring 2 taucht noch einmal auf.
Wie ich schon schrieb: Dann müsste auch
AAA | CCC
als Dublette zu
AAA | BBB
gewertet werden. Willst du das so auswerten?
|
michahe
(Themenstarter)
Anmeldungsdatum: 12. Dezember 2013
Beiträge: 824
|
Danke wxpte, fange ich mal mit den einfachen Teilen an: Dann müsste auch
AAA | CCC
als Dublette zu
AAA | BBB
gewertet werden. Willst du das so auswerten?
Ja, so ist's recht. Wenn du nun die Dubletten finden willst, brauchst du eigentlich nicht jede Zeile einzeln auswerfen, sondern es reicht der Befehl
cat Text.txt | tr -d ' ' | uniq -cd
so leider nicht, es wird ja die ganze Zeile verglichen, nicht die Teilstrings. Aber die Idee ist sehr "übersichtlich", lässt sich das für die Teilstrings erweitern?
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12829
|
Ich weiß, Du wolltest das ja nur in der Shell haben, aber man kann natürlich auch ein Ruby-Skript aus einem Shell-Skript ausführen: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | #!/usr/bin/ruby
last = []
ARGF.each_line do |line|
line.sub! /\s*#.*$/, '' # only here to remove comments
fields = line.split(/\s*\|\s*/).each(&:strip!)
fields.reject!(&:empty?)
if fields.size == 1 || fields.size > 3 ||
fields.first == last.first || fields[0, 2] == last[0, 2]
puts line
end
last = fields
end
|
In awk wird das deutlich schwieriger.
|
michahe
(Themenstarter)
Anmeldungsdatum: 12. Dezember 2013
Beiträge: 824
|
Vielen Dank rklm: Ich weiß, Du wolltest das ja nur in der Shell haben, aber man kann natürlich auch ein Ruby-Skript aus einem Shell-Skript ausführen:
Aber Ruby beherrsche ich schon gleich gar nicht und aus Deinem Skript kann ich nicht einmal eine einzelne Zeile nachvollziehen. In awk wird das deutlich schwieriger.
Warum awk (das ist für mich auch ein Buch mit sieben Siegeln)? Ich denke an so etwas (das verstehe ich zumindest):
Einlesen der Textdatei zeilenweise Zählen der Teilstrings mit echo $zeile | grep -o "|" | wc -l Anzahl in ein Array{Zeilennummer, 0} Teilstrings zerlegen mit echo $zeile | cut -d '|' -f N) mit N = 1 / 2 / 3, Teile in das Array{Zeilennummer, N} Anschließend Array für alle Zeilennummern durchlaufen und auf Dubletten prüfen, bei Dubletten Array{Zeilennummer, N} in Ergebnis.txt schreiben.
Ist das grundsätzlich falsch?
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12829
|
michahe schrieb: Vielen Dank rklm: Ich weiß, Du wolltest das ja nur in der Shell haben, aber man kann natürlich auch ein Ruby-Skript aus einem Shell-Skript ausführen:
Aber Ruby beherrsche ich schon gleich gar nicht und aus Deinem Skript kann ich nicht einmal eine einzelne Zeile nachvollziehen.
Ich hätte jetzt gedacht, dass das durch die Variablen- und Methodennamen recht lesbar ist - zumindest von der Grundstruktur.
In awk wird das deutlich schwieriger.
Warum awk (das ist für mich auch ein Buch mit sieben Siegeln)?
Weil das ein recht mächtiges Werkzeug ist, eine Datei zeilenweise zu bearbeiten, und es praktisch überall vorhanden ist. Das wäre meine nächste Wahl.
Ich denke an so etwas (das verstehe ich zumindest):
Die komplette?
Zählen der Teilstrings mit echo $zeile | grep -o "|" | wc -l Anzahl in ein Array{Zeilennummer, 0} Teilstrings zerlegen mit echo $zeile | cut -d '|' -f N) mit N = 1 / 2 / 3, Teile in das Array{Zeilennummer, N} Anschließend Array für alle Zeilennummern durchlaufen und auf Dubletten prüfen, bei Dubletten Array{Zeilennummer, N} in Ergebnis.txt schreiben.
Ist das grundsätzlich falsch?
Das "Array für alle Zeilennummern" ist natürlich der Killer bei großen Dateien. Man will so etwas möglichst mit wenig Daten machen, die man zwischenspeichert. Und das geht ja hier auch gut, weil die Eingabe sortiert ist. Erst alles in den Speicher zu saugen ist nicht mal mit der Shell nötig. Due kannst im Prinzip den selben Ansatz wie meinen nehmen - nur dann halt mit Shell-Tools.
|
wxpte
Anmeldungsdatum: 20. Januar 2007
Beiträge: 1179
Wohnort: Schäl Sick
|
michahe schrieb: Danke wxpte, fange ich mal mit den einfachen Teilen an: Dann müsste auch
AAA | CCC
als Dublette zu
AAA | BBB
gewertet werden. Willst du das so auswerten?
Ja, so ist's recht. Wenn du nun die Dubletten finden willst, brauchst du eigentlich nicht jede Zeile einzeln auswerfen, sondern es reicht der Befehl
cat Text.txt | tr -d ' ' | uniq -cd
so leider nicht, es wird ja die ganze Zeile verglichen, nicht die Teilstrings. Aber die Idee ist sehr "übersichtlich", lässt sich das für die Teilstrings erweitern?
Unter den Voraussetzungen klappt das mit uniq leider nicht so einfach. Aber für deine ursprüngliche Idee, jedes Duplikat als eigene Zeile auszuwerfen, habe ich jetzt einen Ansatz:
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 | #!/bin/bash
while read zeile; do
zeile=$(sed 's/$/|/' <<< $zeile | tr -s '|') # Trenner werden für readarray vervollständigt
readarray -td'|' tstr <<< $zeile
if ! grep -q '[[:graph:]]' <<< "${tstr[1]}";then # Es sind weniger als zwei Teilstrings vorhanden
echo "$zeile" >> wgr2.tmp
elif grep -q '[[:graph:]]' <<< "${tstr[3]}"; then # Es sind mehr als drei Teilstrings vorhanden
echo "$zeile" >> mhr3.tmp
fi
if grep -q '[[:graph:]]' <<< "${tstr[1]}" && ! grep -q '[[:graph:]]' <<< "${tstr[2]}"; then # Wenn die Zeile zwei Teilstrings hat
if grep -o -m2 "${tstr[0]}" $1 /dev/null;then # Teilstring 1 taucht noch einmal auf
grep -o -m1 "${tstr[0]} *| *${tstr[1]}" $1 >> dupl.tmp
fi
elif grep -q '[[:graph:]]' <<< "${tstr[2]}" && ! grep -q '[[:graph:]]' <<< "${tstr[3]}"; then # Wenn die Zeile drei Teilstrings hat
if grep -o -m2 "${tstr[0]} *| *${tstr[1]}" $1 /dev/null;then # Die Verkettung von Teilstring 1 und Teilstring 2 taucht noch einmal auf
grep -o -m1 "${tstr[0]} *| *${tstr[1]} *| *${tstr[2]}" $1 >> dupl.tmp
fi
fi
done < $1
echo 'Es sind weniger als zwei Teilstrings vorhanden:'
cat wgr2.tmp
echo
echo 'Es sind mehr als drei Teilstrings vorhanden:'
cat mhr3.tmp
echo
echo 'Die folgenden Duplikate wurden gefunden:'
cat dupl.tmp
rm *.tmp
|
Es sind weniger als zwei Teilstrings vorhanden:
AAA |
Es sind mehr als drei Teilstrings vorhanden:
AAA | DDD | EEE | FFF|
Die folgenden Duplikate wurden gefunden:
AAA | BBB
AAA | BBB
AAA | CCC
AAA | DDD | EEE
AAA | DDD | EEE
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12829
|
rklm schrieb: Ich stelle gerade fest, dass die Bedingungen nicht richtig waren: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | #!/usr/bin/ruby
last = []
ARGF.each_line do |line|
line.sub! /\s*#.*$/, '' # only here to remove comments
fields = line.split(/\s*\|\s*/).each(&:strip!)
fields.reject!(&:empty?)
if fields.size <= 1 || fields.size > 3 ||
fields.first(fields.size - 1) == last.first(fields.size - 1)
puts line
end
last = fields
end
|
|
wxpte
Anmeldungsdatum: 20. Januar 2007
Beiträge: 1179
Wohnort: Schäl Sick
|
wxpte schrieb: ...
Vor
/dev/null
fehlt in meinem Skriptansatz leider die Umleitung über > , das hatte ich übersehen. Richtig wäre dort jeweils:
> /dev/null
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11179
Wohnort: München
|
Man könnte das auch mit einem Set machen, wenn die Dubletten in der Datei nicht zwingen hintereinander stehen müssen:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | #!/usr/bin/env python3
import fileinput
seen = set()
for line in fileinput.input():
l, *_ = line.partition('#')
fields = tuple(filter(None, (f.strip() for f in l.strip().split('|'))))
fl = len(fields)
if fl == 1:
print(line, end='')
continue
matchme = tuple(fields[:2]) if fl > 2 else fields[0]
if fl > 3 or matchme in seen:
print(line, end='')
seen.add(matchme)
|
|
wxpte
Anmeldungsdatum: 20. Januar 2007
Beiträge: 1179
Wohnort: Schäl Sick
|
Die Suche nach den Duplikaten war noch nicht fehlerfrei. Aber jetzt:
AAA |
AAA | BBB
AAA | BBB
AAA | CCC
BBB | CCC
CCC | DDD
CCC | DDD
AAA | DDD | EEE
AAA | DDD | EEE
BBB | III | EEE
CCC | EEE | FFF
CCC | EEE | FFF
AAA | III | EEE
AAA | DDD | EEE | FFF
Das Skript:
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 | #!/bin/bash
while read zeile; do
zeile=$(sed 's/$/|/' <<< $zeile | tr -s '|') # Trenner werden für readarray vervollständigt
readarray -td'|' tstr <<< $zeile
if ! grep -q '[[:graph:]]' <<< "${tstr[1]}";then # Es sind weniger als zwei Teilstrings vorhanden
echo "$zeile" >> wgr2.tmp
elif grep -q '[[:graph:]]' <<< "${tstr[3]}"; then # Es sind mehr als drei Teilstrings vorhanden
echo "$zeile" >> mhr3.tmp
fi
if grep -q '[[:graph:]]' <<< "${tstr[1]}" && ! grep -q '[[:graph:]]' <<< "${tstr[2]}"; then # Wenn die Zeile zwei Teilstrings hat
if [ $(cut -f1 -d\| $1 | grep -c "${tstr[0]}") -gt 1 ];then # Teilstring 1 taucht noch einmal auf
grep -o -m1 "${tstr[0]} *| *${tstr[1]}" $1 >> dupl.tmp
fi
elif grep -q '[[:graph:]]' <<< "${tstr[2]}" && ! grep -q '[[:graph:]]' <<< "${tstr[3]}"; then # Wenn die Zeile drei Teilstrings hat
if [ $(cut -f1,2 -d\| $1 | grep -c "${tstr[0]} *| *${tstr[1]}") -gt 1 ];then # Die Verkettung von Teilstring 1 und Teilstring 2 taucht noch einmal auf
grep -o -m1 "${tstr[0]} *| *${tstr[1]} *| *${tstr[2]}" $1 >> dupl.tmp
fi
fi
done < $1
echo 'Es sind weniger als zwei Teilstrings vorhanden:'
cat wgr2.tmp
echo
echo 'Es sind mehr als drei Teilstrings vorhanden:'
cat mhr3.tmp
echo
echo 'Die folgenden Duplikate wurden gefunden:'
cat dupl.tmp
rm *.tmp
|
Es sind weniger als zwei Teilstrings vorhanden:
AAA |
Es sind mehr als drei Teilstrings vorhanden:
AAA | DDD | EEE | FFF|
Die folgenden Duplikate wurden gefunden:
AAA | BBB
AAA | BBB
AAA | CCC
BBB | CCC
CCC | DDD
CCC | DDD
AAA | DDD | EEE
AAA | DDD | EEE
CCC | EEE | FFF
CCC | EEE | FFF
|
michahe
(Themenstarter)
Anmeldungsdatum: 12. Dezember 2013
Beiträge: 824
|
Ganz herzlichen Dank wxpte, Dein Skript funktioniert mit Real-Daten schon sehr gut. Nur die von Dir bereits erwähnte Stelle: wxpte schrieb: michahe schrieb:
b. Es werden Zeilen nur mit identischer Anzahl Teilstrings (entweder beide haben zwei ODER beide haben drei Stück) verglichen. Wenn die Zeile zwei Teilstrings hat: Teilstring 1 taucht noch einmal auf. Wenn die Zeile drei Teilstrings hat: Die Verkettung von Teilstring 1 und Teilstring 2 taucht noch einmal auf.
Wie ich schon schrieb: Dann müsste auch
AAA | CCC
als Dublette zu
AAA | BBB
gewertet werden. Willst du das so auswerten?
macht mir noch Probleme, weil der zweite Teilstring mal dieses (Dubletten-relevant) und mal jenes (nicht Dubletten-relevant) enthält. Dafür muss ich mir (mit etwas mehr Ruhe als gerade jetzt) noch eine Logik einfallen lassen.
|