voitc
Anmeldungsdatum: 25. März 2025
Beiträge: 2
|
Liebe Community,
ich habe bereits viele Einträge hier zu sed und auch die man-pages durch und mir gelingt es einfach nicht ☹ Ich habe eine html-Datei, in der (oft auch mehrere) Dopplungen hintereinander stehen. Z.B. "10a, 10a, 10a, 10b, 10b, 10c, 10d" Da möchte ich per Skript diese doppelten Einträge herauswerfen.
Mein Ansatz war: | sed -i 's/10a\{,}\{\}10a\{,}\{\}/10a\{,}\{\}/g' test.html
|
ebenso mit 10b, 10c, bzw. 10d
Diese würde ich dann als Schleife einfach 10-20 Mal durchlaufen lassen. Die Datei ist aber danach unverändert. Ich tippe auf Fehler beim Komma, bzw. Leerzeichen. Habe auch schon \d044 bzw. \d032 benutzt, auch ohne Erfolg.
Danke vorab an eure Hilfe! Viele Grüße
|
frostschutz
Anmeldungsdatum: 18. November 2010
Beiträge: 7777
|
Was bezwecken deine {} in diesem Kontext? Kanns auch ein 110a, 10a oder 10a, 10ab geben? Da würde 10a, 10a ja auch drauf matchen, obwohl nicht doppelt. Deswegen ist das schwierig mit sed (korrekt) umzusetzen. Das ist dann der Punkt, an dem du dein Glück mit awk versuchst. Oder mit python. Oder ... Wenn du den kompletten String extrahieren kannst evtl. auch was mit uniq (arbeitet zeilenweise also müsste man Kommas ersetzen).
|
Mylin
Anmeldungsdatum: 23. Juli 2024
Beiträge: 231
|
| for i in {10..20}; do
for letter in a b c d; do
sed -i "s/\($i$letter,\s*\)\{1,\}$i$letter/\1/g" test.html
done
done
|
Dürfte mit so ziemlich jeder Kombination funktionieren.
|
TK87
Anmeldungsdatum: 8. Juli 2019
Beiträge: 241
Wohnort: Aachen
|
Moin, das geht auch in einem Durchgang. frostschutz schrieb: Kanns auch ein 110a, 10a oder 10a, 10ab geben?
Falls nein: | sed -i -E '
s/(10a(, )?)+/\1/g;
s/(10b(, )?)+/\1/g;
s/(10c(, )?)+/\1/g;
s/(10d(, )?)+/\1/g
' test.html
|
.
Gruß Thomas
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 13140
|
Einfache Wiederholung | $ echo "10a, 10a, 10a, 10b, 10b, 10c, 10d" | sed 's#\(10.\) *, *\1#<\1>#g'
<10a>, 10a, <10b>, 10c, 10d
$ echo "10a, 10a, 10a, 10b, 10b, 10c, 10d" | sed 's#\(10.\) *, *\1#\1#g'
10a, 10a, 10b, 10c, 10d
|
Mehrfache Wiederholung | $ echo "10a, 10a, 10a, 10b, 10b, 10c, 10d" | sed 's#\(10.\)\( *, *\1\)*#<\1>#g'
<10a>, <10b>, <10c>, <10d>
$ echo "10a, 10a, 10a, 10b, 10b, 10c, 10d" | sed 's#\(10.\)\( *, *\1\)*#\1#g'
10a, 10b, 10c, 10d
|
Die < und > dienen nur der Illustration der Ersetzung. Das Muster in der ersten Klammer muss man ggf. anpassen - je nach Bedarf.
|
voitc
(Themenstarter)
Anmeldungsdatum: 25. März 2025
Beiträge: 2
|
Vielen herzlichen Dank für die vielen schnellen Antworten.
@rklm:
funktioniert teilweise, immer noch Dopplungen drin, aber auch unnötige Kommata (10a, , ,). @TK87:
funktioniert gut, aber auch hier teilweise Dopplungen drin (10a, 10a, 10b, 10c).
Ich habe den oberen Vorschlag ohne perl verwendet, da keine der genannten Variationen enthalten sind. @Mylin:
Dein Vorschlag enthält auch noch Dopplungen, setzt die ersetzten in eckige Klammern und ändert seltsamerweise die Spaltenbreite der Tabelle. @frostschutz:
Es war ein Versuch meinerseits mit den Leerzeichen und Kommata fertig zu werden, da mir nicht vertraut ist, wie ich die regulären Ausdrücke für diese Zeichen in einem Skript handeln muss. Ich verfolge den Vorschlag von TK87 weiter, im Anhang ist ein anonymisierter Schnipsel der html-Datei.
- test.html (4.9 KiB)
- Download test.html
|
TK87
Anmeldungsdatum: 8. Juli 2019
Beiträge: 241
Wohnort: Aachen
|
rklm schrieb: | sed 's#\(10.\)\( *, *\1\)*#<\1>#g'
|
Cooler Ansatz. Ich wusste gar nicht, dass man die Gruppen auch im Suchstring schon wieder benutzen kann. voitc schrieb: @TK87:
funktioniert gut, aber auch hier teilweise Dopplungen drin (10a, 10a, 10b, 10c).
Ist klar, in deinem Schnippsel sind ja teils auch noch CRLF enthalten. Die muss man dann im RegEx natürlich auch mitnehmen.
Ich verfolge den Vorschlag von TK87 weiter, im Anhang ist ein anonymisierter Schnipsel der html-Datei.
Sollen die 09er und 11er auch behandelt werden? Ich mache mal einen Mix aus meinem und rklm's Vorschlag:
| sed -zi -E 's/((09|1[01])[a-f])(,[ \r\n]*\1)+/\1/g' test.html
|
|
Marc_BlackJack_Rintsch
Ehemalige
Anmeldungsdatum: 16. Juni 2006
Beiträge: 4661
Wohnort: Berlin
|
sed/reguläre Ausdrücke sind bei HTML ja sowieso schon nicht wirklich geeignet, aber such für die Unteraufgabe ist das umständlich bis ungeeignet. An der Beispieldatei sieht man ja zum Beispiel, dass die Doppelungen gar nicht direkt aufeinanderfolgen müssen. Hier mal der Inhalt der zweiten Tabellenzeile mit allen Vorkommen von 11a hervorgehoben. Und auch 11b und 11c kommen mehrfach vor, durch andere Klassennamen unterbrochen.
10a,
10a, 11a, 11a, 11a, 11a, 11a, 11a, 11a, 11a, 11b, 11b, 11b, 11b, 11b,
11b, 11c, 11c, 11b, 11c, 11c, 11c, 11c, 11c, 10c, 11a, 11b
Falls ich die Aufgabe richtig interpretiere, sollte das alles sinnvollerweise durch folgendes ersetzt sein im Ergebnis:
10a, 10c, 11a, 11b, 11c
Kann man vielleicht auch mit regulären Ausdrücken machen, wenn man Spass am puzzlen hat, und keinen Wert darauf legt, dass man das selbst in einem Jahr noch versteht, aber ich würde das eher sauberer mit den passenden Werkzeugen lösen. Also eine Programmiersprache, einen HTML-Parser um gezielt die erste Spalte der Tabelle zu bearbeiten, und dann den Inhalt der Zellen an Kommas aufteilen, die Teilzeichenketten in eine Menge stecken, und die für die Ausgabe sortieren. Ginge in Python beispielsweise so:
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 | #!/usr/bin/env python3
import bs4
def main():
with open("Downloads/test.html", "rb") as html_file:
soup = bs4.BeautifulSoup(html_file, "html.parser")
#
# Die Klassenlisten sind in der ersten Spalte der Tabelle mit der CSS-Klasse
# `mon_list`.
#
for row_node in soup.find("table", "mon_list").find_all("tr"):
#
# Die Klassenliste ist entweder direkt in der Zelle oder noch einmal in
# einem <span>-Element verpackt.
#
node = row_node.find("td")
span_node = row_node.find("span")
if span_node:
node = span_node
node.string = ", ".join(
sorted({part.strip() for part in node.string.split(",")})
)
with open("test2.html", "w", encoding="utf-8") as html_file:
html_file.write(str(soup))
if __name__ == "__main__":
main()
|
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 13140
|
Marc_BlackJack_Rintsch schrieb: sed/reguläre Ausdrücke sind bei HTML ja sowieso schon nicht wirklich geeignet,
In diesem Fall aber schon, weil der Text ja Teil eines normalen Text-Elements ist. Da kann ein HTML-Parser ja auch nur so weit helfen, dass er das String-Element extrahiert.
An der Beispieldatei sieht man ja zum Beispiel, dass die Doppelungen gar nicht direkt aufeinanderfolgen müssen. Hier mal der Inhalt der zweiten Tabellenzeile mit allen Vorkommen von 11a hervorgehoben. Und auch 11b und 11c kommen mehrfach vor, durch andere Klassennamen unterbrochen.
10a,
10a, 11a, 11a, 11a, 11a, 11a, 11a, 11a, 11a, 11b, 11b, 11b, 11b, 11b,
11b, 11c, 11c, 11b, 11c, 11c, 11c, 11c, 11c, 10c, 11a, 11b
Falls ich die Aufgabe richtig interpretiere, sollte das alles sinnvollerweise durch folgendes ersetzt sein im Ergebnis:
10a, 10c, 11a, 11b, 11c
Gut, dafür braucht man mehr. Aber natürlich kann ein Regex helfen, den zu ersetzenden Text zu identifizieren und auch die Subelemente (also die zwischen den Kommata) zu parsen.
Kann man vielleicht auch mit regulären Ausdrücken machen, wenn man Spass am puzzlen hat, und keinen Wert darauf legt, dass man das selbst in einem Jahr noch versteht, aber ich würde das eher sauberer mit den passenden Werkzeugen lösen.
Nun ja, reguläre Ausdrücke sind ein passendes Werkzeug für eine Klasse von Parsing-Aufgaben. Ich würde das in meiner Lieblingssprache Ruby so lösen: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 | #!/usr/bin/ruby
require 'nokogiri'
def unify(s)
set = Set.new
s.scan /(\d{2})(\w)/ do |m|
set << [Integer(m[0]), m[1]]
end
set.sort.map {|a| a.join}.join(', ')
end
doc = Nokogiri.parse(ARGF)
doc.xpath('.//span/text()').each do |s|
text = s.to_s[/\d{2}\w(\s*,\s*\d{2}\w)+/]
s.content = unify(text) if text
end
puts doc
|
|
shiro
Anmeldungsdatum: 20. Juli 2020
Beiträge: 1216
|
Hallo voitc, schön, dass du die Testdatei "test.html" beigefügt hast. Nun kann man eher nachvollziehen, was du möchtest. Man kann die Aufgabe auch mit "xmllint", "sort" und "sed" wie folgt machen:
$ xmllint --html --xpath "//tr[@class='list odd']|//tr[@class='list even']" test.html 2>/dev/null |
xmllint --html --xpath "//td[1]" - |
sed -z 's/\ \n//g;s#<[^>]*>##g' |
while read l; do echo -e ${l//, /\\n} | sort -u | sed -z 's/\n/, /g'; echo; done
09b, 09c,
10a, 10c, 11a, 11b, 11c,
11a,
11b, 11c,
$ Obwohl die Aufgabe in einer Zeile beschreibbar ist, habe ich sie in mehrere Zeilen aufgeteilt, um sie leichter zu verstehen. Beschreibung des Ablaufs:
Mit dem 1. "xmllint --html" werden alle "<tr>" Tags (Zeilen) aus der "test.html" Datei extrahiert, die als "class"-Attribut den Wert "list odd" oder "list even" haben. Mit dem 2. "xmllint --html" wird die 1. Spalte einer Tabellen-Zeile (//td[1]) extrahiert. Die manuell im html eingefügten "NewLine" ( ) werden per "sed" gelöscht und alle Tags (<tag>) eliminiert. Übrig bleiben die Komma separierten Elemente. In der "while" Schleife wird die eingelesene Zeile (l) per "echo -e" ausgegeben, wobei das Trennzeichen ", " in einen Zeilenumbruch (\n) konvertiert wird. Diese Element-Zeilen werden per "sort -u" sortiert und eindeutig (-u) ausgegeben. Damit diese "Ergebnis-Zeilen" wieder in einer Zeile dargestellt werden, wandelt "sed -z" die Zeilenumbrüche (\n) in das Trennzeichen ", ". Fertig
PS: hier als 1-Zeiler:
$ xmllint --html --xpath "//tr[@class='list odd' or @class='list even']/td[1]//text()" test.html 2>/dev/null | sed -z 's/\ \n//g' | sed 's/$/"|sort -u/;s/^/echo "/;s/, /\n/ge;s/\n/, /g'
09b, 09c
10a, 10c, 11a, 11b, 11c
11a
11b, 11c
$
Achte auf "//text()" und dem letzten "sed" mit dem "s/../../ge", damit der gebastelte "sort -u" auch gleich im "sed" ausgeführt (e) wird.
|