Lautes Knacken im Kopfhöhrer – ein Workaround

4. August 2014 um 12:00 | Veröffentlicht in Bash, Multimedia, Programmieren, Ubuntuusers | 6 Kommentare
Ich habe mir kurz vor Ostern einen neuen Laptop angeschafft (ein Lenovo Thinkpad E540; mein HP war davor sanft entschlafen). Seit damals habe ich das Problem, dass bei eingestecktem Kopfhörer in unregelmäßigen Abständen ein lautes und sehr unangenehmes Knacken zu hören war. Und das immer direkt bevor eine Audioausgabe gestartet wurde. Sei das nun ein Musikstück in Amarok, ein Video auf YouTube oder ein Snippet zur Bearbeitung in Audacity. Letzteres habe ich besonders in letzter Zeit häufig gemacht und werde ich noch eine Zeit lang tun, wodurch das nervige Problem etwas pressender wurde.
Ich konnte mehrere Faktoren ausschließen. Zum einen bestand das Problem nicht mit meinem Vorgängergerät, was auf ein Problem entweder beim Treiber oder bei der Hardware schließen lässt. Dass ich das Problem sowohl unter Ubuntu 13.10, 14.04, Debian 7 (Sid) und Tanglu Aurora hatte legt ebenfalls diesen Schluss nahe. Auch habe ich mehrere verschiedene Kopfhörer durchprobiert. Manchmal wird ein ein-/ausgestecker Kopfhöhrer gleich, manchmal erst ein paar Sekunden später erkannt. Für mich, als relativer Laie in diesem Gebiet, klingt das wie ein Problem mit der Buchse.
Da ich weder das Wissen, noch das Werkzeug habe um es mir anzusehen oder zu reparieren habe ich mich entschieden einen Workaround zu basteln.

Der Workaround

Mir ist aufgefallen, dass das Knacksen niemals auftritt, wenn irgendein anderes Programm eine Audioausgabe produziert. Die Idee ist also: schreib ein Programm/Skript, welches wenig Ressourcen benötigt, welches eine permanente Audioausgabe macht und welches so unbemerkt wie möglich agiert.
Meine Lösung:
  • Ein Bash-Skript, welches
  • das Programm play aufruft, welches
  • die Datei silence.ogg abspielt, welche 12 Stunden Stille beinhält
Das Programm  play benötigt die Installation des Paketes  sox. Die Datei  silence.ogg habe ich schnell mal in  Audacity kreiert. Sie kommt auf eine Größe von ca. 3,8 MB. Natürlich passt auch jedes andere Format.
Hier ist das Skript:
#!/bin/bash

FILE=silence.ogg
DIR="$HOME/Musik"
if [[ $(ps -ef | grep play | grep "$FILE" -c) -eq 0 ]]; then
    echo "Silencer on"
    play -q "$DIR/$FILE" &
else
    echo "Silencer off"
    pid="$(ps -O command -C play | grep $FILE | tail -
 
shopt -s extglob
    pid=“${pid##*( )}“
    shopt -u extglob
 
    pid=“$(echo „$pid“ | cut -f1 -d‘ ‚)“
    kill "$pid"
fi
Die Zeile
if [[ $(ps -ef | grep play | grep $FILE -c) -eq 0 ]]; then
prüft ob das Skript bereits ausgeführt wird. Dazu holen wir uns mit ps -ef eine Liste aller Prozesse, filtern sie mit grep nach dem String silence.ogg und besorgen uns mit dem Schalter -c die Anzahl der gefundenen Zeilen. Wenn diese 0 ist, dann wird der Prozess noch nicht ausgeführt und wir wollen ihn starten. Nach einer entsprechenden Meldung wird das Programm play mit der gewünschten Datei im Hintergrund (dafür das ‚&‚) gestartet. Der Schalter -q sorgt dafür, dass play selbst keine Ausgabe erzeugt.
Sollte das if nicht zutreffen, also bereits ein Prozess mit dem Skript laufen, begeben wir uns ins else. Hier besorgen wir uns die Programm ID, kurz: pid. In der Zeile
pid=“$(ps -O command -C play | grep $FILE | tail -n1)“
wird dem Programm ps der Schalter -O command übergeben. Damit wird die PID zusammen mit dem jeweiligen Befehl ausgegeben. Die PID steht in der ersten Spalte, kann aber Leerzeichen vorangestellt haben. Mit dem zusätzlichen Schalter -C filtern wir die Liste gleich nach Prozessen des Programmes play. Danach wenden wir grep an um aus den übrig gebliebenen Zeilen die Prozesse zu bekommen, die die Datei silence.ogg verwenden. Da die erste Zeile von ps eine Überschrift ist holen wir uns die Zeile am unteren Ende mittels tail -n1. Sollte es mehrere entsprechende Prozesse geben wird nur der unterste damit gefunden. Man kann natürlich eine Schleife basteln, welche einfach Prozesse beendet, solange es welche gibt. Da sich das Skript aber theoretisch bei jedem zweiten Aufruf selbst beendet, sollte nie mehr als ein Prozess gleichzeitig laufen.
Die Zeile
pid=“${pid##*( )}“
schneidet nun alle vorangestellten Leerzeichen ab. Damit diese Syntax funktioniert haben wir die Shell Option (shopt) Extended Globbing (extglob) aktiviert (-s). Diese ermöglicht der Bash die Verarbeitung von etwas mächtigeren Regulären Ausdrücken. Da wir sie später nicht mehr brauchen, deaktivieren wir sie danach sofort wieder. Nach allem was ich gelesen habe schadet es aber nicht sie in der .bashrc generell zu aktivieren, da sie nur aus Kompatibilitätsgründen deaktiviert ist. Dann kann man diese beiden Zeilen natürlich entfernen.
Mittels dem Befehl cut schneiden wir nun die erste Spalte aus unserer Zeile. Dadurch sollte nun nurmehr die PID übergeblieben sein.
pid=“$(echo „$pid“ | cut -f1 -d‘ ‚)“
Jetzt können wir dem Kommando kill diese pid übergeben und die „Ausgabe“ unserer Stille damit beenden.
Das ist mein Workaround. Für einfachere Ideen oder Vorschläge das Grundproblem anzugehen bin ich jederzeit offen.

=-=-=-=-=
Powered by Blogilo

Forkbombs – oder wie man sich selbst in den Fuß schießt

25. Mai 2014 um 13:09 | Veröffentlicht in Bash, Programmieren, Ubuntuusers | 1 Kommentar

Als eine Forkbomb bezeichnet man ein außer Kontrolle geratenes Programm oder Skript. Das fragliche Programm „forkt“ sich ununterbrochen, erzeugt also immer neue Prozesse. Dadurch erhalten wichtige Prozesse weniger CPU-Zeit und wichtige Daten werden nach und nach aus dem RAM in den Swap übertragen. Mit anderen Worten: das System wird in die Knie gezwungen. Als Form einer DDoS-Attacke kann eine Forkbomb zum Angriff auf ein System verwendet werden. Manchmal passiert es einem aber auch, dass man sich unabsichtlich eine Konstruktion baut, welche wie eine Forkbomb agiert.

Vor etwa 18 Monaten schrieb ich hier einen Artikel über „Alternativen zu youtube-dl„, in welchem ich terminalbasierte Möglichkeiten untersucht habe Videos von YouTube für die offline-Aufbewahrung herunterzuladen. Damals wollte ich mich nicht näher mit dem Problem beschäftigen und habe mir ein einfaches, kleines Wrapper-Skript für cclive geschrieben. Vor kurzem sprach ich das Problem gegenüber einem Kollegen an und wir entschlossen uns dem Problem auf den Grund zu gehen. Der Erfolg ließ nicht lange auf sich warten, nach etwa einer halben Stunde war das Mysterium gelöst. Ich hatte, aus Bequemlichkeit, ein Skript geschrieben welches youtube-dl mit einer Option aufrief, die ich auch sonst immer an das Programm anhing. Da ich zu dieser Zeit gerade meine aliases aufgeräumt hatte, schrieb ich dafür ein kleines Wrapper-Skript mit folgendem Inhalt:

#!/bin/bash

youtube-dl -t

Das Skript benannte ich dann, wieder aus Bequemlichkeit, youtube-dl. Das Skript legte ich ab unter $HOME/bin, welches in meiner PATH-Variable eingetragen war. Voila, Si Forkbömb ist fertisch.</fakeFrenchAccent>

Für alle, die den Ablauf noch nicht nachvollziehen können: Wenn das Programm mit youtube-dl aufgerufen wurde, wurde in Wahrheit das Skript unter $HOME/bin aufgerufen. Dieses sollte dann das eigentliche Programm aufrufen, rief jedoch einfach wieder das Skript unter $HOME/bin auf …

Das ist natürlich ein peinlicher Fehler, der eigentlich einfach zu finden gewesen sein sollte. Er fällt allerdings unter die Kategorie „How did this ever work?„, denn ich hatte das Skript zum Zeitpunkt als die Probleme auftraten schon eine Zeit lang in Gebrauch.

Gegenmittel

Man sieht wie leicht es ist sich selbst in eine Forkbomb zu bauen. Was ist aber nun, wenn man sich mit einer Forkbomb, ob absichtlich oder nicht, konfrontiert sieht? Die englische Wikipedia bietet dazu ein paar Vorschläge. Der meiner Ansicht nach eleganteste ist ein zsh-Einzeiler:

while (sleep 100 &) do; done

Dieser Code erzeugt immer neue Instanzen des Programmes sleep. Das ‚&‚ sorgt dafür, dass der Aufruf in einen neuen Prozess ausgelagert wird. Dadurch können beliebig viele Prozesse gestartet werden, welche sich alle nach jeweils 100 Sekunden wieder beenden. Dadurch hat man eine „kontrollierte“ Forkbomb geschaffen, welche dem Schadprogramm langsam das Wasser abgräbt, bis es sich nicht mehr forken kann. Nach knapp 2 Minuten sollte man wieder die Kontrolle über das System erhalten.

Um schnell auf eine Forkbomb reagieren zu können sollte der obige Code als Skript vorliegen um schnell aufrufbar zu sein. Dazu schreibt man den Code in eine Textdatei:

#!/bin/zsh
while (sleep 100 &) do; done

und benennt die Datei möglichst eindeutig wie kurz. Ich habe sie einfach ff genannt (ForkFighter!). Diese Datei legt man nun in einem Verzeichnis ab, welches man in der PATH-Variable eingetragen hat.

=-=-=-=-=
Powered by Blogilo

LaTeX – Grafiken einbinden, Tabellen basteln, Referenzen innerhalb und außerhalb des Dokumentes, Präsentationen mit Beamer und Sonstige Kleinigkeiten

22. Juni 2013 um 05:28 | Veröffentlicht in LaTeX, Programmieren, Ubuntuusers | 3 Kommentare

Inzwischen ist der Workshop abgeschlossen. Die beiden letzten Termine (4.6. und 18.6.) lagen so nah bei einander, dass ich unter den Vorbereitungen keine Zeit hatte die Dateien zu veröffentlichen. Daher fasse ich die Veröffentlichungen gleich in einem Artikel zusammen.

Der erste Workshop befasste sich mit Grafiken, Tabellen und Referenzen. Im Zweiten ging es dann um Präsentation. Im Anschluss wurden noch ein paar zusätzliche Formatierungsmöglichkeiten und technische Details gezeigt.

Dateien

Grafiken.tex   <– TeX-File für Grafiken
Grafiken.pdf   <– Kompiliertes PDF
Tabellen.tex   <– TeX-File für Tabellen
Tabellen.pdf   <– Kompiliertes PDF
Referenzen.tex   <– TeX-File für Referenzen
Referenzen.pdf   <– Kompiliertes PDF

Presentation.tex   <– TeX-File für Präsentation
Presentation.pdf   <– Kompiliertes PDF
Sonstiges.tex   <– TeX-File zu sonstigen Themen
Sonstiges.pdf   <– Kompiliertes PDF

Einheit01.bib   <– Das zugehörige Bib-File (Zitiermaterial) (update)
include.tex   <– Header-File mit allen notwendigen Layout-Informationen und eingebundenen Paketen (update)
cc-by-sa.png   <– CC-Grafik

Für Presentation.tex

Taach.png  <– Ja, ich habe das Logo des Blogs als Beispiel verwendet
Taach.jpeg  <– Selbes Bild, anderes Dateiformat

Für Sonstiges.tex

Farbennamen.png  <– Ein Haufen vordefinierter Farben
geraetetreiber1.png  <– Verschiedene Gerätetreiber
geraetetreiber2.png  <– Verschiedene Gerätetreiber
geraetetreiber3.png  <– Verschiedene Gerätetreiber

Anmerkung:  Das Original aus dem Workshop hat auf der Titelfolie noch eine zusätzliche Grafik: das Logo des Mentoring-Programms der Uni Wien. Da ich dieses nicht unter eine CC-Lizenz  stellen kann habe ich es kurzerhand herausgenommen. Außerdem habe ich den Header noch nicht aufgeräumt. Hier ist ein Haufen an neuen Kommandos, welche keine logische Struktur haben. All das in Version 2.0 🙂

Wie auch im PDF geschrieben: alle diese Dateien stehen unter einer CC-BY-SA 3.0, mit einer Ausnahme: die cc-by-sa.png-Grafik, an welcher alleine Creative Commons die Rechte hält.

=-=-=-=-=
Powered by Blogilo

LaTeX – Mathe-Modus und BibTex

8. Mai 2013 um 06:09 | Veröffentlicht in LaTeX, Ubuntuusers | 10 Kommentare

Wie ich schon vor längerer Zeit geschrieben habe, halte ich in diesem Semester, zusammen mit zwei Kollegen, am Informatik-Institut der Uni Wien einen LaTeX-Workshop ab. Dieses Mal waren die Themen, die wir vorgetragen haben Mathematik-Modus (Formelsetzen) und BibTeX (Zitieren).

Selbstverständlich ist auch diesen Folien der Umfang dieses Teilbereiches von LaTeX nicht erschöpft, sie sollen nur einen Punkt zum Einstieg bieten.

Beim nächsten Termin (4.6.) wird es um Tabellen erstellen, Grafiken einbinden und Referenzen setzen (innerhalb und außerhalb des Dokuments) gehen.

Dateien

Mathe-Modus.tex  <– TeX-File für den Mathe-Modus
Mathe-Modus.pdf  <– Kompiliertes PDF
BibTeX.tex  <– TeX-File für BibTex
BibTeX.pdf  <– Kompiliertes PDF

Einheit01.bib  <– Das zugehörige Bib-File (Zitiermaterial)
include.tex  <– Header-File mit allen notwendigen Layout-Informationen und eingebundenen Paketen
cc-by-sa.png  <– CC-Grafik

Anmerkung: Das Original aus dem Workshop hat auf der Titelfolie noch eine zusätzliche Grafik: das Logo des Mentoring-Programms der Uni Wien. Da ich dieses nicht unter eine CC-Lizenz  stellen kann habe ich es kurzerhand herausgenommen. Außerdem habe ich den Header noch nicht aufgeräumt. Hier ist ein Haufen an neuen Kommandos, welche keine logische Struktur haben. All das in Version 2.0 🙂

Wie auch im PDF geschrieben: alle diese Dateien stehen unter einer CC-BY-SA 3.0, mit einer Ausnahme: die cc-by-sa.png-Grafik, an welcher alleine Creative Commons die Rechte hält.

=-=-=-=-=
Powered by Blogilo

Horner – Konvertieren zwischen Zahlensystemen

8. April 2013 um 19:25 | Veröffentlicht in Bash, Free Software/Open Source, Programmieren, Ubuntuusers | 11 Kommentare

„Mama, weißt du wie ich von einem Zahlensystem in ein anderes umrechne?“

„Nein, Schätzchen. Weiß Linux es nicht?“

„Nein, ich kann kein Programm finden, dass das kann.“

„Es gibt kein Programm um zwischen Zahlensystemen zu Konvertieren? Oh, nein …“

*Türe wird aufgestoßen, Horner-man stürmt herein*

„Fürchtet euch nicht, gesetzestreue Linuxer! Eure Klagen wurden erhöhrt!“

„Ooh, Horner-man. Unser Held!“

Autsch. Das tut weh. Ich habe es selbst geschrieben und es schmerzt trotzdem sehr. Abgegriffenes Szenario, unglaubwürdige Darsteller, ein Hauch von Sexismus. Autsch.

Kommen wir zum Thema. Seit Langem wollte ich ein Programm haben, mit dem ich einen Wert in verschiedenen Zahlensystemen darstellen kann. Dazu musste dieses Programm jedoch die Umrechnung zwischen diesen Systemen beherrschen. Mein vorrangiges Ziel war die Umrechnung vom Dezimalsystem ins Binärsystem und umgekehrt. Es gibt sicherlich Programme da draußen, die dafür geschrieben sind oder es zumindest nebenbei beherrschen. Speziell die Konvertierung zwischen Zahlensystemen zur Basis 2, 8, 10 und 16 findet sich häufig in digitalen Taschenrechnern. Speedcrunch ist ein schönes Beispiel dafür. Allerdings wollte ich nicht einfach nur ein Programm, welches mir ein Ergebnis anzeigt, sondern auch die Möglichkeit dieses Programm in einem Script zu verwenden. Es musste also konsolenbasiert arbeiten und Ein- und Ausgabe sollten über die Standardkanäle erfolgen.

Da ich kein entsprechendes Programm gefunden habe, habe ich mir vorgenommen selbst eines zu schreiben. Da ich außerdem schon länger das Horner-Schema testweise implementieren wollte,  entschied ich mich dazu, die Umrechnung damit vorzunehmen. Dazu muss erwähnt werden: ich habe dieses Programm für meine persönlichen Bedürfnisse entwickelt, weswegen es nur mit ganzen Zahlen umgehen kann. Die Berechnung mit Gleitkommazahlen ist um einiges komplizierter und es braucht ein bisschen Hirnschmalz um ein Programm zu entwickeln, das nicht sofort Probleme mit der Rechengenauigkeit bekommt. Vielleicht erweitere ich das Programm irgendwann in diese Richtung. Vorerst kann es nur Integer verwerten. Dafür kann es mit Zahlen verschiedenen Basen arbeiten. Die Untergrenze ist Basis 2 (Binär), die Obergrenze ist 36. Rein theoretisch könnte es natürlich noch mehr Basen verarbeiten, bei 36 gehen mir jedoch die sinnvollen Zahlenrepräsentationen aus (0-9, a-z).

Das Programm, oder mehr die Befehlssammlung, kann positive und negative Zahlen von 0 bis 4294967295 verwerten. Dabei ist jedoch 4294967295 das Minimum an Obergrenze. Auf einem 64-bit System ist die Obergrenze 18446744073709551615 (abhängig vom Compiler; hier: GCC). Installation und Verwendung werden noch beschrieben. Zuerst jedoch eine kleine Exkursion:

Was ist das Horner-Schema?

Das Horner-Schema wurde von William George Horner entwickelt und dient der Polynomberechnung. Da die Umrechnung in ein anderes Zahlensystem als Polynom dargestellt werden kann ist das Horner Schema (oder Horner’s Method im Englischen) eine sehr einfache und schnelle Art der Berechnung. Im Großen und Ganzen funktioniert es so:

Umwandlung in das Dezimalsystem; B ist die Basis, a ist die Ausgangszahl, a1 die erste Stelle derselben, …:
(((a1*B + a2)*B + a3)*B + a4) ...

Bsp:
Wir wollen den Binärwert (Basis = 2) 101010 in das Dezimalsystem umwandeln:
((((1*2+0)*2+1)*2+0)*2+1)*2+0 = 42

Die Umwandlung eines Dezimalwertes in ein anderes System funktioniert analog dazu. Zuerst wird per Modulo (Restwertdivision) der Restwert errechnet. Dieser stellt bereits eine Stelle des Ergebnisses dar. Da dieser Rechenvorgang genau umgekehrt zur Umwandlung ins Dezimalsystem verläuft, ist auch das Ergebnis umgekehrt:
42   % 2 = 0
42-0 / 2 = 21
21   % 2 = 1
21-1 / 2 = 10
10   % 2 = 0
10-0 / 2 = 5
 5   % 2 = 1
 5-1 / 2 = 2
 2   % 2 = 0
 2-0 / 2 = 1
 1   % 2 = 1
 1-1 / 2 = 0

Sobald die Zahl, mit der man rechnet 0 geworden ist, endet die Rechnung. Die fettgedruckten Zahlen ergeben, von unten nach oben gelesen, das Ergebnis. Die Zeichenkette ist also 101010. Das ist dieselbe Zeichenkette, die wir zuvor ins Dezimalsystem umgewandelt haben. Es funktioniert!

Die Programme

Ich habe für jeden Rechenvorgang ein C++-Programm geschrieben. 2dec wandelt eine Zahl in das Dezimalsystem um, dec2 wandelt eine Dezimalzahl in eine beliebige Basis (von 2 bis 36) um. Die beiden Programme können jedes für sich aufgerufen werden mit:
dec2 <Basis> <Wert>
2dec <Basis> <Wert>

Die <Basis> ist immer die Basis aus der, bzw. in die ich umrechnen möchte. Die jeweils andere Basis ist ja das Dezimalsystem.

Möchte ich nun den Wert 42 in das Binärsystem umrechnen, so rufe ich das Programm dec2 auf:
dec2 2 42

Möchte ich die Berechnung umdrehen, so verwende ich das Programm 2dec:
2dec 2 101010

Die beiden Programme lassen sich kombinieren, wenn ich von einer Basis in eine andere konvertieren möchte und keine von beiden die Basis 10 ist. Möchte ich wissen, welchen Binärwert der Hexadezimale Ausdruck affe hat, dann mache ich das so:
dec2 2 $(2dec 16 affe)

Ergebnis: 1010111111111110

Das Script

Diese Kombination übernimmt das Script horner. Dieses übernimmt per -i die Basis des Inputs und per -o die Basis, in die der Wert umgewandelt werden soll (Output):
horner -i 16 -o 2 affe
entspricht der obigen Kombination der beiden Programme. Das Script geht davon aus, dass die beiden Programme in einem Verzeichnis abgelegt sind, das in der $PATH-Variablen des Nutzers eingetragen ist.

Das Makefile

Am Besten ist es, wenn man den Code selbst kompiliert. Für all diejenigen, die sich dabei unwohl fühlen oder es einfach noch nicht gemacht haben: keine Sorge, das Makefile übernimmt diese Arbeit. Dazu muss das Paket automake im System installiert sein.

Installation

Zuerst benötigt man den Code, den es hier zum herunterladen gibt.

Dieses Archiv lässt sich per
tar -xvzf Horner.tar.gz
ins aktuelle Verzeichnis entpacken. Die entpackten Dateien liegen dann im selben Verzeichnis wie das Archiv.

Sobald man die Dateien entpackt hat muss man nur in einem Terminal
make
aufrufen. Dies kompiliert den Code und erzeugt die beiden Programme 2dec und dec2.

Mittels
make install
kann man die Programme dann automatisch installieren. Dabei werden sie im Verzeichnis /bin im Home-Verzeichnis des Nutzers abgelegt. Dieses Verzeichnis sollte immer im $PATH liegen und der Nutzer hat dort immer Schreibrechte. Möchtest du die Installation anpassen (sprich: die Dateien woanders unterbringen), dann musst du nur die Dateien 2dec, dec2 und horner im gewünschten Verzeichnis ablegen.

Abschlussbemerkung

Die Programme, das Script und das Makefile stehen unter der GPLv3. Sie sind in keinster Art und Weise getestet und implementieren so gut wie keine Fehlerbehandlung. Über Bugmeldungen freue ich mich, kann aber nicht versprechen, dass ich sie schnell behebe. Wie erwähnt: die Programme sind aus Neugier entstanden und erfüllen meine Anforderungen. Deswegen habe ich vorerst auch nicht vor, sie auf GitHub oder sonstwo hochzuladen. Wenn du das gerne tun möchtest, tu dir keinen Zwang an. Informiere mich in diesem Fall aber bitte darüber.

Über den Inhalt der Programme und des Scripts verliere ich hier keine großen Worte. Wenn du Interesse hast zu erfahren was hier genau passiert, dann schreib mir das in den Kommentaren. Ich antworte dir dann entweder dort oder schreibe einen neuen Artikel zum Thema.

=-=-=-=-=
Powered by Blogilo

LaTeX – Ein Einstieg

13. März 2013 um 07:48 | Veröffentlicht in LaTeX, Ubuntuusers | 7 Kommentare

Lange ist es her, dass ich etwas über LaTeX geschrieben habe. An sich habe ich einen Einstieg in LaTeX schon vor längerer Zeit einmal beschrieben. Dieses Semester leite ich, gemeinsam mit zwei Kollegen an der Uni Wien einen LaTeX-Workshop, in welchem wir versuchen Studienanfänger an LaTeX heranzuführen. Dazu haben wir ein paar Foliensätze zusammengestellt, mit denen wir das jeweilige Thema vorstellen. Die Themen sind folgende:

  • Einstieg/Installation von LaTeX
  • Mathematik-Modus
  • Zitieren mit BibTeX
  • Grafiken verwenden
  • Tabellen bauen
  • Referenzen innerhalb und außerhalb des Dokuments
  • Präsentationen mit Beamer

Wir sind immer noch dabei Foliensätze zu erstellen, deswegen kann ich jetzt nur den ersten Foliensatz online stellen. Die Foliensätze sind so verfasst, dass sie möglichst selbsterklärend sind. Deswegen werde ich hier nicht viel über den Inhalt verlieren. Natürlich besteht LaTeX aus wesentlich mehr als die Folien abdecken können. Es soll eben nur ein Einstieg sein. Weitere Foliensätze folgen, sobald sie in einer finalen Version vorliegen.

Dateien

Einheit01.tex  <– Das haupt-TeX-File
Einheit01.bib  <– Das zugehörige Bib-File (Zitiermaterial)
include.tex  <– Header-File mit allen notwendigen Layout-Informationen und eingebundenen Paketen
cc-by-sa.png  <– CC-Grafik
Einheit01.pdf  <– Fertiges PDF

Anmerkung: Das Original aus dem Workshop hat auf der Titelfolie noch eine zusätzliche Grafik: das Logo des Mentoring-Programms der Uni Wien. Da ich dieses nicht unter eine CC-Lizenz  stellen kann habe ich es kurzerhand herausgenommen. Außerdem habe ich den Header noch nicht aufgeräumt. Hier ist ein Haufen an neuen Kommandos, welche keine logische Struktur haben. All das in Version 2.0 🙂

Wie auch im PDF geschrieben: alle diese Dateien stehen unter einer CC-BY-SA 3.0, mit einer Ausnahme: die cc-by-sa.png-Grafik, an welcher alleine Creative Commons die Rechte hält.

=-=-=-=-=
Powered by Blogilo

Playr – Ein iPod Shuffle für die Bash

21. Januar 2013 um 10:05 | Veröffentlicht in Bash, Multimedia, Programmieren, Ubuntuusers | 9 Kommentare

Schon länger habe ich nach einer Möglichkeit gesucht ein paar Musikdateien auszuwählen und in zufälliger Reihenfolge abspielen zu lassen. Das kann sowohl ein einzelnes Album sein, als auch eine Zusammenstellung mehrere Tracks aus verschiedenen Verzeichnissen. Außerdem sollte dieses Programm Konsolen-basiert sein, da ich es für schnelle Wiedergabe brauche und nicht zuerst in einer GUI alles zusammenklicken will. Da ich ein solches Programm leider noch nicht gefunden habe, habe ich mir selbst ein Bash-Skript zusammengestellt, welches als Scheduler dient und play (Paket sox aus den Quellen) zum Abspielen verwendet. play kann sowohl OGG, FLAC und WAV abspielen. Nach Installation von libsox-fmt-mp3 auch MP3.

Playr

Playr steht für die Zusammensetzung aus play (dem Programm fürs Abspielen der einzelnen Tracks) und random (engl.: zufällig). Dass es aussieht wie ein Web2.0-Name ist wiederum Zufall (dt.: coincidence).

Die grobe Funktionsweise: Die übergebenen Musiktracks werden in eine versteckte Textdatei geschrieben. Dann wird mit Hilfe von $RANDOM eine Zufallszahl zur Berechnung des nächsten Tracks verwendet. Dieser Track wird sodann abgespielt und aus der Textdatei entfernt. Das verhindert, dass dieser erneut abgespielt wird (was etwas ist, dass ich bei portablen Audio-Playern sehr nervig finde). Solange noch Tracks in der Liste sind wird fortgefahren.

Soll die Wiedergabe abgebrochen werden, muss der Nutzer nur [strg]+[c] drücken. Leider habe ich bis jetzt keine Möglichkeit gefunden zusätzlich dazu auch einfach zum nächsten Track zu wechseln. Man kann natürlich einfach die trap entfernen, dann bricht [strg]+[c] nur das aktuelle play ab. Dann kann jedoch das Skript selbst nicht mehr komfortabel beendet werden.

Das Skript

Wir beginnen mit der trap. Dieses Konstrukt sorgt dafür, dass unser Skript [strg]+[c] verarbeiten kann. Dafür schreiben wir folgende Zeile:
trap quit SIGINT SIGTERM

Diese Zeile führt dazu, dass beim Erhalt der Signale SIGINT (Signal 2) oder SIGTERM (Signal 15) quit ausgeführt wird. Nun ist quit kein Bash-Befehl sondern eine Funktion, die wir uns selbst schreiben, welche das Skript geordnet beendet:
function quit {
    rm -f $TRACK_FILE $TEMP_FILE
    exit 1
}

Diese Funktion schreiben wir vor die Trap. In dieser löschen wir einfach unsere Textdateien, damit sie beim nächsten Programmaufruf nicht stören.

Als nächstes definieren wir zwei Variablen für unsere versteckten Textdateien. Zum einen eine Datei um die Tracklist zu speichern, zum Anderen eine Datei in der die veränderte Liste gespeichert wird:
TRACK_FILE=/tmp/.tracklist.playr
TEMP_FILE=/tmp/.temp.playr

Wir speichern die Dateien in  /tmp , damit sie keine Verzeichnisse zumüllen. Außerdem sollten wir in  /tmp  immer Schreibzugriff haben. Sollte  /tmp  nicht verfügbar sein, kann man das Verzeichnis ja auf  $HOME  ändern.

Nun wirds Zeit sicherheitshalber Überbleibsel einer vorherigen Ausführung zu entfernen:
rm -f $TRACK_FILE $TEMP_FILE

Nur für den Fall, dass etwas total schief gelaufen ist.

Um die Tracklist zu erzeugen schreiben wir nun einfach alle Parameter, die beim Skriptaufruf übergeben wurde in das TRACK_FILE:
for file in " $@ "; do
    echo " $file "
done > $TRACK_FILE

Das funktioniert gewisserweise wie eine foreach-Schleife. Eine beliebige Anzahl Parameter wird durchlaufen ( $@ ). Der jeweils aktuelle Parameter wird in  $file  gespeichert und in der Schleife verarbeitet. Hier wird er einfach an die Standardausgabe geschickt. Die komplette Ausgabe der Schleife wird wieder auf  $TRACK_FILE  umgebogen, wodurch die Ausgabe nicht auf der Kommandozeile erscheint, sondern in der angegebenen Datei.

Die Größe der Tracklist holen wir uns mit wc:
size=$( cat "$TRACK_FILE" | wc -l)

Natürlich könnte man die Datei auch gleich als Parameter für wc angeben:
wc -l " $TRACK_FILE "

Dann jedoch erhält man eine Ausgabe nach folgendem Muster:
6 .tracklist.playr

Man muss also erst noch den eigentlichen Wert extrahieren. Da mache ich lieber den „Umweg“ über cat. Ist kürzer.

Nun kommt die eigentliche Hauptschleife des Programmes:
while [[ $size -gt 0 ]]; do
    ## Code
    ((size–))
done

Diese Schleife läuft solange durch, bis  $size  den Wert 0 hat. Da  $size  auf jeden Fall größer als 0 sein muss und in jedem Durchgang um eins verringert wird, ist das irgendwann der Fall. Mit der Abbruchbedingung verhindern wir auch, dass die Schleife ausgeführt wird, wenn die Tracklist leer ist.

Es ist Zeit. Zeit um die Urväter, die Auditoren, die Götter anzurufen. Zeit eine Zufallszahl zu erzeugen. Einer Zufallszahl innerhalb eines Intervalls errechnet man am Besten mithilfe von Modulo. Modulo ist der Name der in der Unterstufe als Restwertdivision bekannten Berechnung.

Kleine Beispiele:  10 / 3 = 3 . Rest:  1 13 / 5 = 2 . Rest:  3 . Uns interessiert immer der Restwert. Dieser ist auf jeden Fall immer  um  1  kleiner als der Divisor (für diejenigen, die schon lange aus der Schule draußen sind 🙂 ).

Bash scheint keine built-in Funktion für Modulo zu haben, dafür gibt es ein paar andere Möglichkeiten eine solche Berechnung durchzuführen. Am ansprechendsten habe ich  let  gefunden:
let "rand = $RANDOM % $size + 1"

In der Variable  $rand  wird das Ergebnis der Berechnung festgehalten.  $size  ist natürlich der Begrenzer.  $RANDOM  ist eine Umgebungsvariable, welche bei jedem Aufruf einen neuen zufälligen Integer (Ganzzahl) ausgibt. Da diese Berechnung bei einer Tracklist-Größe von z.B.  6  den Wertebereich von  0-5  abdeckt, wir aber kein  0 tes Lied, dafür aber ein  6 tes, zählen wir zum Ergebnis einfach  1  dazu.

Den damit errechneten Track erhalten so:
track= " $( head  $TRACK_FILE  -n$rand | tail -n1)

Hier passiert ein bisschen was:
head  -n$rand

filtert die ersten  $rand  Zeilen aus der Liste. Ist  $rand also  2 , dann erhalten wir durch  head   die ersten  2  Zeilen. Dadurch ist der gesuchte Track immer an letzter Stelle in der Liste. Diese letzte Stelle können wir uns nun per  tail  holen:
tail  -n1

Hier holt uns  tail  die  1 ste Zeile von hinten. Damit haben wird unseren Track. Dieser wird nun in die Variable  $track  gespeichert.

Jetzt wird es Zeit den Track abzuspielen:
play " $track "

Natürlich kann man auch das Ausrechnen des Tracks und den Aufruf von  play  in einer Zeile unterbringen. Ich bin jedoch eher für lesbaren Code als für „Zeileneffizienz“.

Zum Abschluss muss noch eine neue Tracklist angelegt werden, ohne den gerade abgespielten Track. Dazu holen wir uns zuerst alle Tracks vor dem Aktuellen:
head " $TRACK_FILE " -n$[ $rand-1 ] > " $TEMP_FILE "

und die Tracks danach:
tail " $TRACK_FILE " -n$[ $size-$rand ] >> " $TEMP_FILE "

Mit
$rand-1

holen wir uns alle Tracks vor dem Aktuellen und sparen diesen aus. Hingegen
$size$rand

liefert uns alle Tracks nach dem Aktuellen. Damit erhalten wir wieder dieselbe Liste wie zuvor, nur ohne dem letzten Track. Man beachte die Pfeile, welche das Ergebnis jeweils nach $TEMP_FILE schicken. In der ersten Zeile befindet sich nur ein Pfeil. Das bedeutet, dass die Datei überschrieben und neu befüllt wird. Es wird also nur das gespeichert, was in dieser Zeile herauskommt. In der zweiten Zeile hingegen sind zwei Pfeile. Das bedeutet, das das Ergebnis an die Datei angehängt wird. Der Inhalt der Datei wird also nicht über-, sondern der neue Inhalt dahinter in die Datei geschrieben.

Schließlich müssen wir die neue Tracklist noch über die alte schreiben:
mv " $TEMP_FILE " " $TRACK_FILE "

Nach der Schleife selbst rufen wir noch einmal quit auf, damit das Skript sich auch ohne Abbruch durch den Nutzer sauber beendet.

Das vollständige Skript

#!/bin/bash

function quit {
     rm  -f $TRACK_FILE $TEMP_FILE
     exit  1
}

trap  quit SIGINT SIGTERM

TRACK_FILE=/tmp/.tracklist.playr
TEMP_FILE=/tmp/.temp.playr

rm  -f $TRACK_FILE $TEMP_FILE

for  file  in  $@ do
     echo   $file
done > $TRACK_FILE

size=$( cat  „$TRACK_FILE“ |  wc  -l)

while [[  $size -gt 0 ]]; do
     let  „rand =  $RANDOM  %  $size  + 1″
    
track= " $( head  $TRACK_FILE  -n$rand  tail  -n1)
     play  $track
     head   $TRACK_FILE  -n$[ $rand-1 ] >  $TEMP_FILE
     tail   $TRACK_FILE  -n$[ $size$rand ] >>  $TEMP_FILE
     mv   $TEMP_FILE “ „ $TRACK_FILE
    ((size–))
done

quit

Zum Schluss

Ich bin vergleichsweise ein Anfänger in Shell-Script. Ich habe einige Erfahrung in C++ und es kann daher sein, dass ich versuche Konzepte daraus in Bash umzusetzen, obwohl es wesentlich einfachere Lösungen gibt. Wenn du eine bessere Lösung weißt, schreibe sie doch bitte in die Kommentare.

Lizenz

Das Skript steht unter der GPLv3.

Alternativen zu youtube-dl

13. Dezember 2012 um 21:28 | Veröffentlicht in Bash, Free Software/Open Source, Multimedia, Programmieren, Ubuntuusers | 17 Kommentare

Ich mag youtube-dl. Es ist einfach zu benutzen, ist plattformunabhängig (Python) und braucht nicht viele Ressourcen (Terminal-Anwendung).

Leider mag youtube-dl mich zur Zeit nicht. Sobald ich versuche ein Video herunterzuladen schaufelt die Anwendung meinen Arbeitsspeicher voll, sodass alles andere in den Swap ausweichen muss. Das führt dazu, dass das System innerhalb von Sekunden nicht mehr reagiert. Am längsten überlebt noch die Maus, welche sich etwa eine halbe Minute lang stark ruckelnd bewegen kann. Danach ist auch sie tot und das Einzige das hilft ist ein Hard-Reset. Keine wünschenswerte Situation.

Ich habe bereits
youtube-dl -U
als sudo ausgeführt um ein update des Scripts zu erzwingen. Das hat leider nichts geholfen. Der Streaming-Dienst ist auch egal. Ob YouTube, Vimeo oder sonst was, das Problem ist immer gleich.

Logischer Schritt: ich schaue mich nach Alternativen um. Wenn man per
apt-cache search
nach youtube sucht, werden einem bereits eine Liste an Programmen angeboten, welche in der Lage sind Videos von Streaming-Services herunterzuladen. Darunter befinden sich

  • clive
  • cclive
  • fatrat
  • slimrat
  • nicovideo-dl
  • get-flash-videos
  • metacafe-dl
  • nomnom

clive

Speziell clive hat meine Aufmerksamkeit erregt. Es ist ein Terminal-Tool, welches sich für Streaming-Services eignet, die ihre Videos in einem Flash-Player abspielen. Es ist leichtgewichtig, hat eine Vielzahl an praktischen Optionen und verhält sich allgemein sehr wie ich es von youtube-dl gewohnt bin.

Ein einfaches
clive <video_url>
lädt das Video an der angegebenen URL herunter. An sich kann man damit leben. Allerdings hat clive eben mehrere interessante Funktionen, mit denen man sich gerne spielt. Dazu gehört einmal der Schalter -F. Damit erhält man eine Liste von verfügbaren Formaten und Qualitätsstufen in denen das Video vorhanden ist. Diese Liste kommt im Format
hd|mobile|sd
Die durch „|“ getrennten Optionen kann man dann in einem zweiten Aufruf dem Schalter -f angeben, welcher dann dafür sorgt, dass clive das Video in der gewünschten Qualität herunterlädt.

Da es etwas unbequem ist für jedes Video zwei Programm-Aufrufe zu starten, habe ich ein einfaches Skript geschrieben, welches diese Aufgabe abnimmt:

#!/bin/bash

VIDEO_URL="$1"
TEMP_FILE=".video-dl_temp_file"

clive -F "$VIDEO_URL" > "$TEMP_FILE"

CHOICES=$(cat "$TEMP_FILE" | tail -n1)

echo -n "Choose format (${CHOICES%% *}|best): "
read FORMAT

clive "$VIDEO_URL" --format="$FORMAT"
--filename-format="%t_$FORMAT.%s"

rm -f "$TEMP_FILE"

Was passiert hier?

VIDEO_URL=“$1
Wir speichern die angegebene URL in einer Variable.

TEMP_FILE=“.video-dl_temp_file
Der Einfachheit halber brauchen wir eine temporäre Textdatei, in welcher wir Informationen speichern können. Der Punkt am Anfang des Dateinamens bedeutet, dass die Datei versteckt ist. Sie wird also im Dateibrowser, oder per ls nicht angezeigt. Die Datei wird am Ende des Skripts wieder gelöscht.

clive -F „$VIDEO_URL“ > „$TEMP_FILE
Hier holen wir uns die möglichen Formate, in denen das Video vorliegt und schreiben diese Ausgabe in die temporäre Datei. Da es sich hierbei um zwei Zeilen handelt und nur die zweite Zeile relevant ist, brauche ich:

CHOICES=$(cat „$TEMP_FILE“ | tail -n1)
Diese Zeile gibt mir den Inhalt der temporären Datei aus (cat), schneidet die letzte Zeile ab (tail) und speichert die gewünschte Zeile in der Variablen CHOICES.

Als Nächstes fragen wir den Nutzer, welches Format bevorzug wird:
echo -n „Choose format (${CHOICES%% *}|best): 
wobei
${CHOICES%% *}
dafür sorgt, dass hinter der Liste nichts mehr steht. clive gibt hier nochmal die Adresse des Videos aus. Dieser Ausdruck schneidet sie weg.

Dann lesen wir die Wahl des Nutzers ein:
read FORMAT

Nun ist es an der Zeit endlich das Video herunterzuladen:
clive „$VIDEO_URL“ –format=“$FORMAT
–filename-format=“%t_$FORMAT.%s

–filename-format gibt die Formatierung des Dateinamens an. %t und %s sind dabei Platzhalter, welche für den Titel und das Suffix der Datei stehen. Zusätzlich zu den Platzhaltern kann man noch beliebig weitere Vorgaben für den Dateinamen machen. Ich füge zwischen Dateinamen und Suffix noch das Format der Datei ein.

Nachdem die Video-Datei sicher auf der Platte liegt lösche ich noch die temporäre Datei:
rm -f „$TEMP_FILE

Fazit

Bis jetzt habe ich clive mit YouTube und Vimeo getestet. Beides funktionierte sehr gut und machte keine Probleme. Das Skript ist natürlich ausbaufähig. Zur Zeit kann es immer nur mit einem Video gleichzeitig umgehen und möglicherweise findet man noch einen besseren Weg als die Informationen in einer temporären Datei zu speichern. Für meine aktuellen Zwecke reichts.

clive steht unter der GPLv3.

[UPDATE]

clive wird inzwischen nicht mehr gepflegt. Stattdessen sollte cclive verwendet werden, welches in der aktuellen Version leicht andere Parameter aufweist. Georg hat in den Kommentaren eine mögliche Version des Skripts mit cclive gepostet.

[/UPDATE]

=-=-=-=-=
Powered by Blogilo

Bash – Bedingte Ausführung mit if und die while-Schleife

4. Juli 2012 um 20:12 | Veröffentlicht in Bash, GNU/Linux, Programmieren, Ubuntuusers | 8 Kommentare

Wie jede vernünftige Programmiersprache hat auch die Bash eine Möglichkeit Code nur dann abzuarbeiten wenn bestimmte Bedingungen eintreffen. Für alle, die mit Programmierung nicht viel zu tun haben: if wird in den meisten Programmiersprachen dazu verwendet das Programm bestimmte Dinge machen zu lassen, abhängig vom Zustand. Grundsätzlich gibt es für die Auswertung eines Audrucks in wahr und falsch das Kommando test. Allerdings ist if normalerweise ein klein wenig übersichtlicher zu verwenden. Ein Beispiel in C:

if( is_closed(WienerWald) ) {
    gehe_zu(McDonalds);
}
else {
    gehe_zu(WienerWald);
}

Dieses Codestück versucht zu ermitteln ob ein bestimmtes Restaurant geschlossen ist. Wenn es geschlossen ist, wird ein anderes Restaurant besucht. Wenn es nicht geschlossen ist, dann wird das fragliche Restaurant besucht. Die Funktion is_closed() liefert uns entweder wahr oder falsch zurück. Dies sind die einzigen Werte die das if selbst auswerten kann. Auf dieser Grundlage wird dann entschieden welcher Code ausgeführt wird. Wenn der Ausdruck wahr zurückliefert, dann wird der Code gleich nach dem if ausgeführt. Wenn der Ausdruck falsch zurückliefert, dann wird der Code nach dem else ausgeführt. Ist der Ausdruck falsch und es gibt jedoch kein else, wird das ganze if einfach übersprungen. So funktioniert das if in allen (mir bekannten) Programmiersprachen. Aus welchen Datenbeständen das Programm diese Information abfragt ist hier uninteressant. Es soll nur die bedingte Ausführung gezeigt werden.

In Bash ist die Syntax ähnlich:

if [ is_closed $WienerWald ]
then
    gehe_zu $McDonalds
else
    gehe_zu $WienerWald
fi

Wichtig dabei zu beachten ist, dass um die eckigen Klammern Leerzeichen stehen müssen, sonst wird ein Fehler ausgegeben und das if versagt. Ich weiß leider selbst nicht, warum die Bash hier so pickig ist. Bei den Klammern handelt es sich um eine Kurzform des Kommandos test, welches für die eigentliche Auswertung des Ausdrucks zuständig ist.

Vergleich zwischen Zahlen

Zwischen den Klammern kann grundsätzlich alles stehen was irgendwie zu wahr oder falsch abgeleitet werden kann. So können auch Zahlen oder Zeichenketten verglichen werden:

if [ $i -lt 10 ]
then
    echo "0$i"
else
    echo "$i"
fi

Dieses if fragt ab ob die Variable $i kleiner (lt = lesser than) ist als der Wert 10. Ist dies der Fall wird der Zahl eine führende Null vorangestellt, ist dies nicht der Fall, dann nicht. Das kann nützlich sein, wenn man bei formatierten Aufzählungen die Formatierung beibehalten will:

08 - ...             8 - ...
09 - ...    statt    9 - ...
10 - ...             10 - ...

Außer lt gibt es noch folgende Befehle zum Vergleichen von zwei Zahlenwerten:

  • eq = equal            = Gleichheit
  • ne = not equal        = Ungleichheit
  • le = lesser or equal  = kleiner oder gleich
  • ge = greater or equal = größer oder gleich
  • gt = greater than     = größer als

Vergleich zwischen Zeichenketten

Auch Zeichenketten, sogenannte strings, können verglichen werden. Hier gibt es mehrere Möglichkeiten:

if [ $str = "Auto" ]

vergleicht ob die Zeichenkette, welche in der Variable $str gespeichert ist, der Zeichenkette „Auto“ entspricht. Wenn dem so ist, dann ist der Ausdruck wahr.

Jetzt wirds ein bisschen kompliziert:

if [ $str != "Auto" ]

vergleicht ebenfalls die Variable $str und die Zeichenkette „Auto„. Dieser Ausdruck ist dann wahr, wenn die beiden Zeichenketten nicht übereinstimmen. Das Rufzeichen agiert hier als Anzeige dafür, dass der gewünschte Vergleich nicht erfolgreich sein soll, damit die Bedingung erfüllt ist.

Als dritte Möglichkeit eine Zeichenkette in einem if zu verwenden kommt die Abfrage ob in der Zeichenkette überhaupt etwas steht:

if [ $str ]

fragt ab ob irgendein Zeichen (egal welches oder wieviele) in der Variable gespeichert sind. Ist mindestens ein Zeichen vorhanden, dann ist der Ausdruck wahr. Ist die Zeichenkette leer (= „“), dann ist der Ausdruck falsch. Zum selben Ergebnis kommt dieser Ausdruck:

if [ -n $str ]

Der Befehl -n prüft ob die Länge der Zeichenkette in der angegebenen Variable nicht Null (non zero) ist. Hingegen

if [ -z $str ]

prüft genau das Gegenteil: -z fragt ob die Länge Null (zero) ist.

UND, ODER und NICHT

Wie jede brauchbare Programmiersprache bietet die Bash bei if auch die Möglichkeit mehrere Ausdrücke hintereinander auszuwerten, ohne jedesmal ein neues if zu benötigen. Der Code

if [ $i -lt 10 ]
then
    if [ $j -lt 10 ]
    then
        ...
    fi
fi

lässt sich kürzer so schreiben:

if [ $i -lt 10 -a $j -lt 10 ]
then
    ...
fi

Das -a (UND) sorgt dafür, dass der Gesamtausdruck nur dann wahr ist, wenn beide Seiten (also links vom -a und rechts davon) jeweils wahr sind. In den meisten Programmiersprachen ist es so, dass zuerst die linke und dann die rechte Seite ausgewertet werden. Sollte die linke Seite bereits falsch sein, wird normalerweise die rechte Seite nicht mehr ausgewertet. Ist im allgemeinen auch nicht notwendig, da der gesamte Ausdruck dann sowieso falsch ist, wenn eine Seite falsch ist. Wahrer als falsch kann es nicht mehr werden. Die Bash hingegen wertet beide Seiten aus. Egal ob der Gesamtausdruck bereits falsch ist oder nicht.

Ähnlich funktioniert das ODER (-o). Hierbei soll geschaut werden ob eine der beiden Seiten wahr ist. Wenn zumindest eine wahr ist, ist der Gesamtausdruck wahr. Auch hier sollte normalerweise der rechte Ausdruck nicht mehr angesehen werden, wenn der Linke bereits wahr ergeben hat. Die Bash machts trotzdem:

if [ $i -lt 10 -o $j -lt 10 ]

Zu guter Letzt im Reich der sogenannten logischen Operatoren gibt es noch das NICHT. Dieses wird als Rufzeichen dargestellt (wie auch schon bei den Vergleichen der Zeichenketten). Seine Aufgabe ist es ein wahr in ein falsch umzukehren und vice versa:

if [ ! $i -lt 10]

Dieser Ausdruck ist dann wahr, wenn $i gerade nicht kleiner ist als 10. Selbstverständlich kann dieses Verhalten auch mit einem anderen Befehl erreicht werden. Im konkreten Fall wäre ein

if [ $i -ge 10 ]

gleichwertig. Das Rufzeichen hat trotzdem seine Existenzberechtigung, weil es manche Ausdrücke wesentlich übersichtlicher werden lässt, da es weniger zu schreiben gibt.

Arbeiten mit Dateien und Ordnern

Es ist mit if in der Bash auch möglich direkt mit Dateien zu interagieren. Ein einfaches

if [ -e datei ]

prüft ob eine Datei mit Namen „datei“ existiert. Dabei wird sich immer auch das aktuelle Verzeichnis bezogen. Man kann natürlich auch einen kompletten Pfad angeben, dann ist es egal in welchen Ordner man sich befindet:

if [ -e /Pfad/zur/datei ]

Bisher kann „datei“ jedoch sowohl Ordner als auch reguläre Datei sein. Um zwischen diesen Möglichkeiten zu unterscheiden gibt es eigene Befehle:

if [ -f datei ]

prüft ob eine reguläre Datei mit Namen „datei“ im aktuellen Verzeichnis liegt. Ist das der Fall, ist das Ergebnis wahr. Es kann natürlich auch ein Ordner mit dem Namen „datei“ im aktuellen Verzeichnis liegen. Dann liefert dieser Ausdruck falsch, da ein Ordner mit diesem Namen zwar existiert, aber ein Ordner keine reguläre Datei ist. Hingegen mit

if [ -d ordner ]

wird konkret geprüft ob ein Ordner mit Namen „ordner“ an dieser Stelle existiert. Hier gilt dasselbe wie bei Dateien: existiert ein Ordner mit angegebenem Namen, ist der Ausdruck wahr. Existiert kein Ordner mit diesem Namen (egal ob eine Datei mit diesem Namen existiert oder nicht) ist der Ausdruck falsch.

if [ -x executable ]

prüft ob die angegebene Datei ausführbar ist. Das kann ein Programm, ein ausführbares Script oder ein Ordner sein.

Soviel zu den wichtigsten Vergleichen bei der Verwendung von if. Eine vollständige Auflistung gibt es auf der GNU-Infopage zu Bash.

while

Zum Abschluss noch ein Hinweis: das if lässt sich mit drei kleinen Änderungen in eine Schleife verwandeln. Eine Schleife ist ein Gebilde dessen Code mehrmals ausgeführt wird bis eine bestimmte Bedingung eintritt:

while [ $i -lt 10 ]
do
    echo $i
    ((i++))
done

Diese Schleife gibt die Zahlen von n (ein Wert der vorher festgelegt wurde) bis 9 aus. Die '((' und '))' lassen uns ungehindert arithmetische Ausdrücke ausführen. i++ bedeutet, dass der Wert, der in $i gespeichert ist um 1 erhöht wird.

  • Das Wort if muss also durch ein while ersetzt werden
  • then muss durch do ersetzt werden
  • fi muss durch done ersetzt werden

Als Ausdruck in den Klammern ist alles möglich das auch im if möglich ist.

Wichtig: eine while-Schleife verfügt nicht über ein else.

=-=-=-=-=
Powered by Blogilo

Bash – Filename Expansion, for-Schleife

3. Juli 2012 um 19:52 | Veröffentlicht in Bash, GNU/Linux, Programmieren, Ubuntuusers | 14 Kommentare

Die Bash ist ja bekanntlich das Schweizer Taschenmesser beim Umgang mit Unixoiden Systemen. Vor 23 Jahren geboren, heute in der Version 4.2 verfügbar, bleibt sie jedoch immer noch für viele ein Mysterium. Primär für Anfänger ist der Umgang mit ihr umständlich und die große Funktionsvielfalt schier erdrückend. Bis man sich hier durchgekämpft und die steile Lernkurve hinter sich gebracht hat, hat sich bereits eine Haarlänge a’la Rapunzel gebildet. Oder ein Bart nach Stallman. Natürlich zähle ich mich selbst bei weitem nicht zu den Gurus unter den Programmierern. Lerne ich jedoch etwas Praktisches, so versuche ich es leicht verständlich weiter zu geben. Heute geht es um Filename Expansion.

Die GNU-Infopage zu Bash-4.2 lässt sich in Kapitel 3.5.3 etwas umständlich über die genaue Wirkungsweise der Filename Expansion aus. Leider ohne ein Beispiel zu geben. Dieses Versäumnis soll nun nachgeholt werden.

Vorbereitung

Nehmen wir an, wir haben eine Variable $file mit folgendem Inhalt:
/home/user/datei.png

Für bisher-nicht-Bash-Nutzer, so weise ich einer Variable in Bash eine Zeichenkette (ein Pfad zu einer Datei ist eine solche) zu:
file="/home/user/datei.png"
Die Variable $file wird, falls sie noch nicht existiert an dieser Stelle angelegt.

Mit dem Befehl echo kann ich überprüfen, welchen Inhalt die Variable hat:
echo $file
Ausgabe: /home/user/datei.png

Man beachte das Dollar-Zeichen wenn die Variable verwendet wird. Nur mit dem Dollar-Zeichen davor kann auf den Inhalt der Variablen zugegriffen werden.

Der Dateiname

Angenommen ich möchte nun aus unserem Dateipfad den Namen der Datei herauslösen. Hierfür muss ich wohl alles wegschneiden, was vor dem Dateinamen steht:
/home/user/datei.png

Das Konstrukt um dies zu erreichen sieht in Bash so aus:
${file##*/}

Man beachte, dass nach wie vor das Dollar-Zeichen zu Anfang stehen muss. Der Name der Variable ist nun in geschwungenen Klammern eingeschlossen. Die Zeichen dahinter teilen sich in zwei Gruppen auf. Das '##' steht für den Befehl alles von links her wegzuschneiden. Alle weiteren Zeichen, welche darauf folgen, geben an was genau weggeschnitten werden soll.

*/
steht für: alles bis zum Zeichen '/'.

Folgender Befehl:
echo ${file##*/}
ergibt also folgende Ausgabe:
datei.png

Nun kann man argumentieren, dass in der Zeichenkette
/home/user/datei.png
ja viele '/' vorkommen. Das stimmt. Das '##' schneidet eben alles bis zum letzten '/' weg.

Ähnlich funktioniert das Konstrukt
${file#*/}
Man beachte das einfache '#'. Dieser Befehl schneidet alles bis zum ersten '/' weg.

Folgender Befehl:
echo ${file#*/}
ergibt also folgende Ausgabe:
home/user/datei.png
Man beachte das fehlende '/' am Anfang.

Das Suffix

Dieses Spielchen funktioniert natürlich auch von der rechten Seite her. Statt dem '##', bzw. dem '#', verwenden wir hier ein '%%', bzw. ein '%'.

Nehmen wir an, wir haben den kompletten Dateinamen (inklusive Endung) in einer weiteren Variablen gespeichert:
file_name=${file##*/}

Ein
echo $file_name
bringt uns nun folgende Ausgabe:
datei.png

Wir wollen nun nur den eigentlichen Dateinamen herausholen, ohne Endung. Dazu müssen wir die Endung wegschneiden:
datei.png

Das funktioniert so:
${file_name%.*}

Das '%' funktioniert hier als Anweisung die Zeichenkette von rechts her zu beschneiden. Weggeschnitten werden soll '.*', also alles was rechts vom ersten '.' steht.
(Natürlich macht hier die Unterscheidung zwischen erstem und letztem Auftreten eines Zeichens keinen Unterschied, da das Zeichen hier nur einmal vorkommt.)

Anwenden

Manch einer fragt sich sicherlich schon: und wofür brauche ich das? Nun, die Bash ist zwar sehr komplex, wenn man sie jedoch mal ein wenig verstanden hat auch sehr flexibel und effizient. Ich erledige viele Aufgaben, für welche ich mit grafischen Programmen viele Klicks und mehrere Minuten brauche (z.B. Bildbearbeitung mit Gimp), in wenigen Sekunden.

Nehmen wir an, wir wollen von unserem Bild ein Thumbnail für eine Website erzeugen. Das Thumbnail soll etwa ein Fünftel der Größe des Originals haben (also 20%). Hierzu verwenden wir das Programm convert, welches zum Paket imagemagick gehört. Der Befehl sieht dann so aus:
file="datei.png"
convert "$file" -resize 20% "${file%.*}_thumb.png"

Damit wird eine neue Datei erzeugt, welche 20% der Größe der Datei datei.png hat und datei_thumb.png heißt. Hier haben wir die Filename Expansion also verwendet um den Dateinamen anzupassen, den wir der neuen Datei gegeben haben.

Auf mehrere Dateien anwenden

Das ist selbstverständlich noch nicht der Weisheit letzter Schluss. Für eine einzelne Datei zeigt sich die Flexibilität von Filename Expansion noch nicht so wirklich. Erst wenn ich mehrere Dateien, mit stark unterschiedlichen Namen bearbeiten will sieht man das Potential.

Nehmen wir also an, wir haben einen Ordner voller Bilder:
Geburtstag1.png
Valerie_Donau.jpg
Visitenkarte.png
Zweiwohnschloss.gif
...

Von allen diesen Bildern möchten wir, wie im obigen Beispiel, ein Thumbnail erzeugen lassen. Wir brauchen nun alle Techniken, die wir oben gelernt haben, um dieses Vorhaben elegant umzusetzen. Zu aller erst brauchen wir eine Schleife. Eine Schleife ist ein Konstrukt, welches den Code, der in der Schleife steht, sooft ausführt, bis eine bestimmte Bedingung erreicht ist. Unsere Schleife sieht so aus:

for file in ./*; do ; done

Diese Schleife macht noch nichts. Hier will ich nur den grundsätzlichen Aufbau erklären. for ist das Schlüsselwort mit dem die Schleife beginnt. Danach wählen wir einen Namen für eine Variable, die wir in der Schleife verwenden wollen (hier: file). in ist wieder ein Schlüsselwort, welches wir bei der Schleife brauchen. Danach steht was in die vorher gewählte Variable eingetragen werden soll. ./* steht für „alle Dateien in diesem Ordner“. Wir gehen davon aus, dass in diesem Ordner nur Bild-Dateien enthalten sind.

Da wir unseren Code nicht in ein Shell-file schreiben, sondern direkt auf die Kommandozeile sind die Semikolons zwingend. Nach jedem Befehl muss ein Semikolon folgen.

do leitet den Code der Schleife ein. Der erste Befehl der Schleife folgt gleich auf das do, ohne Semikolon. Mit done wird die Schleife abgeschlossen. do und done sind keine Befehle, deshalb muss auf sie nicht direkt ein Semikolon folgen.

Die Bedingung dieser Schleife ist: gehe alle Dateien in diesem Ordner durch. Für jede Datei in diesem Ordner wird die Schleife also genau einmal ausgeführt. Der jeweilige Name der Datei findet sich in der Variable $file.

Nun schreiben wir den Code unserer Schleife. Zuerst müssen wir den eigentlichen Namen (das Prefix) der jeweiligen Datei finden:

for file in ./*; do prefix="${file%.*}"; done

Danach brauchen wir noch das Suffix (Endung; denn in unserem Ordner liegen ja Bild-Dateien von verschiedenen Typen herum herum):

for file in ./*; do prefix="${file%.*}"; suffix="${file##*.}"; done

Nun können wir den Konvertierungsbefehl aus dem obigen Beispiel einfügen (mit einer kleinen Änderung):

for file in ./*; do prefix="${file%.*}"; suffix="${file##*.}"; convert "$file" -resize 20% "$prefix"_"thumb.$suffix"; done

Für unsere gegebenen Dateien sieht das Ergebnis so aus:

Geburtstag1.png
Geburtstag1_thumb.png
Valerie_Donau.jpg
Valerie_Donau_thumb.jpg
Visitenkarte.png
Visitenkarte_thumb.png
Zweiwohnschloss.gif
Zweiwohnschloss_thumb.gif
...

Diese Schleife hat bei mir für 70 Bilder zu jeweils ca. 5 MB etwa 1:30 min gebraucht. Schaffst du das mit dem Gimp auch? 🙂

=-=-=-=-=
Powered by Blogilo

Nächste Seite »

Erstelle kostenlos eine Website oder ein Blog auf WordPress.com.
Entries und Kommentare feeds.