Das ist die Art von Artikeln, die ich sehr gerne schreibe. Oft entsteht die Idee dazu, weil mir nichts Besseres einfällt. Dann greife ich gerne auf eigene Herausforderungen oder Anwendungsfälle zurück. Eigentlich wollte ich die nächste Podcastfolge mit Joël über die Alpha 5 des COSMIC-Desktops aufnehmen. Doch dann fiel mir auf, dass wir bereits im Juli 2024 in CIW095 über COSMIC gesprochen haben. Wir haben uns dann schnell auf ein anderes Thema geeinigt; ihr werdet es am Mittwoch in CIW121 hören.
Bei den vielen Artikeln und Podcastfolgen wird es immer schwerer, den Überblick zu behalten. Über was haben wir schon geschrieben? Worüber haben wir bereits gesprochen? Und so ist die Idee für diesen Artikel entstanden. Ich brauche eine Liste aller Podcastfolgen, in der ich schnell nachschauen kann, was wir wann behandelt haben. Während ich diese Einleitung schreibe, habe ich noch keine Ahnung, wie die Lösung aussehen wird.
Wie könnte es gehen?
Zuerst wollte ich ein kleines Python-Skript schreiben, welches sich per FTP mit unserem Server verbindet und dort das Artikelverzeichnis ausliest. Daraus hätte ich die Podcastfolgen extrahiert und die gewünschten Daten aus den Shownote-Artikeln geparst. Unser CMS (Bludit) legt für jeden Artikel ein Verzeichnis an, in dem sich eine Datei mit dem Inhalt befindet.
Dann kam mir in den Sinn, dass wir XML-Dateien für die RSS-Feeder haben. Einen für alle Artikel und einen anderen für den Podcast. Auf unserer Webseite findet man beide ganz unten auf der Seite beim RSS- und dem Mikrofon-Symbol. Bei der Verwendung des Podcast-XMLs würde ich mir das Filtern auf Podcastartikel sparen. Also habe ich diese Datei lokal heruntergeladen; einen Ausschnitt seht ihr im Titelbild.
Mit XML umgehen
Nun stellt sich die Frage, welche Inhalte der XML-Datei ich für meine Liste brauche. Ein Podcast-Eintrag aus der Datei sieht so aus:
<item>
<title>CIW119 - Ditana</title>
<link>https://gnulinux.ch/ciw119-podcast</link>
<description><![CDATA[Wir werfen einen Blick auf die neue Linux Distribution Ditana]]></description>
<content:encoded><![CDATA[<h2>CIW - Folge 119 - 15.01.2025 - Ditana</h2><ul><li>Wir begrüssen alle Distro-Hopper zur Folge 119 von ...</li></ul>]]></content:encoded>
<author>GNU/Linux.ch</author>
<pubDate>Wed, 15 Jan 2025 11:45:35 +0000</pubDate>
<guid>https://gnulinux.ch/ciw119-podcast</guid>
<enclosure url="https://gnulinux.ch/podcast/CIW119.mp3" length="97219675" type="audio/mpeg" />
<itunes:summary>Wir werfen einen Blick auf die neue Linux Distribution Ditana</itunes:summary>
<itunes:author>GNU/Linux.ch</itunes:author>
<itunes:duration>4856</itunes:duration>
<itunes:explicit>no</itunes:explicit>
<itunes:title>CIW119 - Ditana</itunes:title>
</item>
Die Tags <title>, <pubDate> und <itunes:summary> würden mir genügen. Doch wie geht das? Nach kurzer Suche im Internet bin ich auf das CLI-Werkzeug xmllint gestossen. Dabei handelt es sich um einen XML-Linter. Ein Linter ist ein Werkzeug zur statischen Code-Analyse. Xmllint kann XML formatieren, validieren und via Xpath abfragen. So wie ich das einschätze, ist xmllint bei den meisten Distributionen vorinstalliert.
Schritt für Schritt
Im ersten Schritt habe ich den Titel aus der RSS-XML-Datei abgefragt. Das geht so:
xmllint --xpath '//item/title' gnulinux_newscast_rss.xml
<title>CIW120 - Asocial Media Flucht</title>
<title>CIW119 - Ditana</title>
<title>CIW118 - Happy GNU Year</title>
<title>CIW117 - Steuererklärung</title>
...
Den Xpath kann man absolut (/rss/channel/item/title) oder relativ (//item/title) angeben, was nichts am Ergebnis ändert. Dummerweise ist der Text vom Title-Tag umrandet. Zwar gibt es bei xmllint dieses Kommando, welches die Tags entfernt:
xmllint --xpath 'string(//item/title)' xml-Datei
… was leider nur die erste Instanz zurückgibt. Daher habe ich die Ausgabe in eine Datei geschrieben:
xmllint --xpath '//item/title' gnulinux_newscast_rss.xml > title.txt
Um die Titel-Klammer zu entfernen, kam der Stream-Editor sed zum Einsatz; einmal für das vordere Tag und noch einmal für den hinteren Teil. Beim vorderen <title> geht das so:
sed -i 's/<title>//g' title.txt
Der Parameter -i sorgt dafür, dass das Ersetzen direkt in der angegebenen Datei title.txt stattfindet. Mit dem s sagt man, dass etwas ersetzt werden soll. Die Slashes sind Trennzeichen zwischen den Befehlsparametern. Dann gibt man an, was ersetzt werden soll (<title>) und wodurch es ersetzt werden soll (//), also gar nichts. Zum Schluss sagt man mit dem g, dass sich die Ersetzung auf alle Vorkommnisse beziehen soll (global). Danach sieht die Liste so aus:
CIW120 - Asocial Media Flucht</title>
CIW119 - Ditana</title>
CIW118 - Happy GNU Year</title>
...
Der vordere Tag wurde entfernt. Nun könnte man denken, dass sich der hintere Tag genauso einfach entfernen lässt. Theoretisch ja, praktisch steht jedoch der Slash als Trennzeichen im Weg. Der Befehl:
sed -i 's/</title>//g' title.txt
… funktioniert nicht, weil der Slash vor title zur Verwirrung bei sed führt. Ist das jetzt ein Trennzeichen, oder soll das ersetzt werden? Um das zu lösen, gibt es verschiedene Möglichkeiten, mit denen ich euch nicht langweilen möchte. Am einfachsten sagt ihr sed, dass ein anderes Trennzeichen verwendet werden soll, z. B. ein Semikolon:
sed -i 's;</title>;;g' title.txt
Nun sieht das Ergebnis so aus:
CIW120 - Asocial Media Flucht
CIW119 - Ditana
CIW118 - Happy GNU Year
...
… und das ist, was ich haben wollte. Um das Datum herauszufischen, mache ich das Gleiche in Grün:
xmllint --xpath '//item/pubDate' gnulinux_newscast_rss.xml > date.txt
sed -i 's/<pubDate>//g' date.txt
sed -i 's;</pubDate>;;g' date.txt
Doch beim Auslesen der Summary ergeben sich unerwartete Schwierigkeiten:
xmllint --xpath '//item/itunes:summary' gnulinux_newscast_rss.xml > summary.txt
XPath error : Undefined namespace prefix
XPath evaluation failure
Da bockt xmllint wegen des Namespaces in itunes:summary. Man kann in xmllint den Namespace angeben; leider habe ich nicht herausgefunden, wie man das macht. Das ist eine Frage an die Kommentatoren. Daher belasse ich es bei den beiden Feldern title und pubDate. Diese Listen stehen jetzt in den beiden Dateien title.txt und date.txt.
Zusammenführen
Jetzt stellt sich die Frage, wie man diese beiden Dateien zeilenweise zusammenführt. Dafür gibt es eine naheliegende Antwort: Ich kopiere beide Dateien als Spalten in LibreOffice Calc:
Doch geht das auch im Terminal? Nichts einfacher als das:
paste date.txt title.txt > ciw_folgen.txt
Wed, 22 Jan 2025 11:34:04 +0000 CIW120 - Asocial Media Flucht
Wed, 15 Jan 2025 11:45:35 +0000 CIW119 - Ditana
Wed, 08 Jan 2025 11:33:57 +0000 CIW118 - Happy GNU Year
Wed, 18 Dec 2024 11:31:36 +0000 CIW117 - Steuererklärung
Wed, 11 Dec 2024 11:32:34 +0000 CIW116 - Aufmerksamkeitsökonomie
Das Datum könnte man noch auf 22 Jan 2025 reduzieren. Auch das geht mit einem Befehl im Terminal:
cut -b 6-16 date.txt > date_cut.txt
Mit cut schneide ich den Text von Position 6 bis 16 aus und schreibe das Ergebnis in die Datei date_cut.txt.
Zusammenfassung
Was ich hier mit vielen Worten beschrieben habe, lässt sich in einem Shell-Skript vereinen. Das manuelle Herunterladen der XML-Datei entfällt; das Skript erledigt das mit dem wget Befehl.
#!/bin/bash
# Create a list of podcast episodes with date and title
wget -q https://gnulinux.ch/podcast/gnulinux_newscast_rss.xml -O rss.xml
xmllint --xpath '//item/title' rss.xml > title.txt
sed -i 's/<title>//g' title.txt
sed -i 's;</title>;;g' title.txt
xmllint --xpath '//item/pubDate' rss.xml > date.txt
sed -i 's/<pubDate>//g' date.txt
sed -i 's;</pubDate>;;g' date.txt
cut -b 6-16 date.txt > date_cut.txt
paste date_cut.txt title.txt > ciw_folgen.txt
echo 'Suche in: ciw_folgen.txt'
Das Ergebnis in der Datei ciw_folgen.txt sieht so aus:
22 Jan 2025 CIW120 - Asocial Media Flucht
15 Jan 2025 CIW119 - Ditana
08 Jan 2025 CIW118 - Happy GNU Year
18 Dec 2024 CIW117 - Steuererklärung
...
Es geht noch einfacher
Falls ihr euch erinnert, war die Frage, ob ein Thema (z. B.: COSMIC) in einer Podcastfolge bereits behandelt wurde. Obwohl die Ausgabe nicht schön ist, beantwortet dieser Einzeiler die Frage ebenfalls:
curl -s https://gnulinux.ch/podcast/gnulinux_newscast_rss.xml | grep cosmic
Statt dem Gebastel mit xmllint, sed, cut und paste, holt curl die XML-Datei im Silent-Modus und lässt die Suche mit grep darauf los. Vorteil: die vollständigen Shownotes werden durchsucht. Nachteil: das Ergebnis ist unübersichtlich.
Fazit
Es hält sich der hartnäckige Mythos, dass man für Linux die Kommandozeile bedienen und beherrschen muss. Dieser Mythos ist negativ belegt. Fakt ist, dass niemand die Kommandozeile bedienen muss, wenn er oder sie es nicht möchte.
Die Wahrheit ist: Wer das Terminal für sich entdeckt, hat Freude daran und erledigt viele Aufgaben in Null-Komma-Nichts. Traut euch, die Kommandozeile für euch zu entdecken. Die Community hilft gerne dabei.
Die oben beschriebene Aufgabenstellung ist ein anschauliches Beispiel dafür. Nun kann ich eine aktuelle Podcast-Liste in ein paar Sekunden erstellen und weiss immer, welche Themen wir schon besprochen haben. Ohne das Terminal würde ich viel länger brauchen, um diese Liste zu erzeugen.
Titelbild: selbst erstellt
Quellen: stehen im Text
Ich würde es folgender maßen formatieren:
Zur Erklärung:
-F '
: Filtert nach Tagsoder
//
auftaucht, dannsplit(content, variable, delimiter)
, wer schonmal C Programmiert hat kenntprintf
, um eine neue Zeile zu bekommen benötigt man Back-Slash + n:\n
//
, wenn Title Tag gefunden wird, dann wird nur der Titel ohne NewLine ausgeben, dankprintf
tr -s delimiter
wird verwendet, um ein Zeichen nur einmalig vorkommen zu lassen. Oder mittr search replace
um ein bestimmtes Zeichen zu tauschen (ALLE) Bsp.: `tr 's' 'S' - Dies würde jedes kleine s ins Große S verwandelnSo, hätte ich es gemacht. Hoffentlich wird es ordentlich angezeigt. Danke für den netten Artikel, der war wieder etwas erfrischend und hat meine Stimmung gehoben, obwohl ich mich heute etwas Kränklich fühle.
LESSTHAN SLASH ( title PIPE dubDate ) GREATERTHAN hätte ich dies mit Backslash machen müssen?
Test:
Gibt es irgendwo ein FAQ für Zeichen, die erlaubt sind. Meine Antwort macht ja so dann keinen Sinn mehr, die ich zu erst geschrieben habe.
Hallo,
zumindest die Aufrufe von sed kann man sich sparen, wenn man gleich den textuellen Inhalt des Elemenknotens selektiert, also mittels xmllint --xpath '//item/title/text()' gnulinux_newscast_rss.xml bzw. xmllint --xpath '//item/pubDate/text()' gnulinux_newscast_rss.xml Würde man statt xmllint einen voll ausgewachsenen XSLT-Prozessor nutzen, wäre dann auch eine xsl:for-each-Schleife zur Iteration über die jeweiligen Kindelemente (hier: Titel und Datum) jedes item-Elements möglich -- aber das wäre dann vermutlich mit Kanonen auf Spatzen geschossen.
Und die summary-Elemente kann man mittels local-name() auslesen: xmllint --xpath "//item/*[local-name()='summary']/text()" gnulinux_newscast_rss.xml
Ich werfe meinen "HOLZHAMMER" in den Ring, der -falls er geht- sicherlich eleganter ginge 😉️
In Debian 12 ist übrigens xmllint augenscheinlich gar nicht installiert (bei mir), es stünde aber im Paket libxml2utils zur Verfügung.
Ferner gibt es wohl noch ein Tool namens xmlstarlet (neben wahrscheinlich noch etlichen anderen, von denen vmtl. einige hier schon vorgestellt wurden).
...HUCH!... Was ist da denn passiert? – In der "Vorschau" wurde meine Idee glatt "ausgeblendet". Gemeint war, dass man in rsss.xml die folgende Ersetzung vornimmt:
itunes:summary durch itunes_summary
Trivial, und nicht sonderlich elegant. Ja, ich weiß.
Vielen Dank für eure tollen Hinweise. Ich mache es jetzt so:
ohne xmllint, ohne paste , nur mit SED:
wget -q https://gnulinux.ch/podcast/gnulinux_newscast_rss.xml -O rss.txt
sed -n '//,// { // { s/^.(.)/\1/ ; p } // { s/^...., (.) ..:./\1/ ; p } // { s/^.(.*)/\1\n/ ; p } }' < rss.txt | sed -n '1~2!G;h;2~2{s/\n/ /g;p}' > Ergebnis.txt
Hallo Frank. Bitte klammere den Code in drei Gravis: U+0060 https://symbl.cc/de/0060/
Dann sieht es so aus:
Sehr geil! Von mir erhältst du auf jeden Fall den ersten Preis für die nerdigste Lösung!
ich versuche es noch einmal , und vermeide im skript die kombination von schrägstrichen und kleiner und größer zeichen.
funktioniert leider nicht .. auch hier werden die texte mit kleiner und größer zeichen zerstört ..