ubuntuusers.de

sed - Dopplungen in einer html-Datei eliminieren

Status: Gelöst | Ubuntu-Version: Server 24.04 (Noble Numbat)
Antworten |

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:

1
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

Avatar von 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

Avatar von Mylin

Anmeldungsdatum:
23. Juli 2024

Beiträge: 231

1
2
3
4
5
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:

    1
    2
    3
    4
    5
    6
    sed -i -E '
      s/(10a(, )?)+/\1/g;
      s/(10b(, )?)+/\1/g;
      s/(10c(, )?)+/\1/g;
      s/(10d(, )?)+/\1/g
    ' test.html
    

.

  • Falls ja: Statt sed einfach perl mit Parameter -p nutzen. Da kann man dank lookahead & lookbehind auch solche Fälle ausschließen

    1
    2
    3
    4
    5
    6
    perl -p -i -E '
      s/(?<=[> ]|^)(10a(, )?)+(?=[,< ]|$)/\1/g;
      s/(?<=[> ]|^)(10b(, )?)+(?=[,< ]|$)/\1/g;
      s/(?<=[> ]|^)(10c(, )?)+(?=[,< ]|$)/\1/g;
      s/(?<=[> ]|^)(10d(, )?)+(?=[,< ]|$)/\1/g
    ' test.html
    

Gruß Thomas

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13140

Einfache Wiederholung

1
2
3
4
$ 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

1
2
3
4
$ 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:

1
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:

1
sed -zi -E 's/((09|1[01])[a-f])(,[ \r\n]*\1)+/\1/g' test.html

Marc_BlackJack_Rintsch Team-Icon

Ehemalige
Avatar von Marc_BlackJack_Rintsch

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 Team-Icon

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/\&#13;\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" (&#13;) 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/\&#13;\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.

Antworten |