Bash – Filename Expansion, for-Schleife

Juli 3, 2012 um 7:52 pm | 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

14 Kommentare »

RSS feed for comments on this post. TrackBack URI

  1. Guter Artikel! Hoffentlich vergesse ich die Syntax nicht wieder. Man brauchts immer im „falschen“ Moment🙂

    • Das stimmt🙂

      Wichtig ist einfach: Der Stern steht immer auf der Seite die man löschen will. Es geht auch mit Zeichenketten statt dem Stern.
      echo „${file%ng}df“
      würde zum Beispiel aus datei.png datei.pdf machen

  2. Was ich noch nirgends herausgefunden habe ist, wie ich mehrere Dateien von ihren unterschiedlichen Suffix befreien kann, bei denen auch ein *.tar.gz dabei sein könnte.

    Immerhin könnte der Name einer Datei auch so aussehen
    dokument-2011.05.30.odt
    würde ich und auch ihr sicher nie so benennen, ist aber möglich.

    Es müsste also eine Art Best Practice geben, bei der auf eine Liste bekannter Erweiterungen gecheckt wird.

    – Habt ihr sowas mal irgendwo gesehen?
    – Und wie würdet ihr auf *.tar.gz testen

    • Du könntest eine eigene Schleife nur für *.tar.gz-Dateien machen:
      for file in *.tar.gz
      Alternativ könntest du zwei Funktionen schreiben, von denen dir die erste den Prefix und die zweite den Suffix zurückliefert. Hier kannst du dann speziell auf verschiedene Suffixe testen.

      • ok, aber auf alle Fälle muss es eine Art von Gedächtnis statt Regel geben für die Suffix (Plural). In meinem Beispiel wäre das „.odt“ und dann „.30“, letzteres muss ja als Fehler verstanden werden.

        Ich meine das ist schon abkzeptabel. Ich bin bisher auch nur auf drei ernsthafte Sonderfälle gestoßen .tar{.gz,.7z,bz2}

        • Du könntest grundsätzlich prüfen ob sich ein *.tar.* im Dateinamen befindet. In diesem Fall wendest du dann eine Sonderregel an, welche dir das *.{gz|7z|bz2} entfernt. Danach hat die Datei ja nur mehr die Endung *.tar und kann damit mit der normalen Regel verarbeitet werden.
          Für die Sonderregel genügt es aber natürlich nicht nur auf „tar“ zu prüfen, weil diese Zeichenkette ja auch regulärer Teil des Namens sein kann.

          • Ok, zum Beispiel so.

            ~$ fullname=“filename.tar.gz“
            ~$ prefix=$(echo ${fullname%.*} | sed s/.tar$//)

            Uppercase würde hier noch nicht berücksichtigt. Immerhin Posix kompatibel. Aber die Suffix bekomme ich nicht in einer Zeile hin. Ne Idee?

  3. Sehr nett! Vielen dank für dieses tutorial.

  4. Wunderbar verständlich erklärt! Bitte mehr davon😉

    • Gerne, sobald ich wieder etwas verstanden habe.

      Mit Filename Expansions habe ich mich seit etwa 2 Jahren herumgequält. Erst gestern kam aber der aha-Effekt.

  5. Danke für den Tipp, die genannten File Expansions kannte ich noch nicht.

    Ich hätte vielleicht noch die eingebaute Substitution erwähnt, die meiner Meinung nach auch extrem hilfreich ist: ${file/jpg/JPEG}

    • Sehr gut, die Substitutionen kannte ich wiederum nicht.🙂

  6. Schöner Artikel! Ich selbst verwende für solche Sachen oft die Programme ‚dirname‘ und ‚basename‘. Sie sind natürlich nicht ganz so leistungsfähig, dafür aber sehr leserlich.

    • Selbstverständlich, die kann man auch verwenden.
      Das ist ja das schöne an Linux/UNIX. Es gibt meist mehrere Wege um ans Ziel zu kommen. Jeder nimmt den Weg der ihm/ihr am Besten zusagt.


Schreibe einen Kommentar

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s

Erstelle eine kostenlose Website oder Blog – auf WordPress.com.
Entries und Kommentare feeds.

%d Bloggern gefällt das: