Vinzenz Mai: Fragen zur Bash

Beitrag lesen

Hallo

Ich hoffe, dass hier ein paar Linux/Bash Experten zu finden sind ;)

als Experten würde ich mich nicht bezeichnen, aber ich wohne bestimmt außerhalb der Stadt, bin somit Fachmann [1]:-)

Vorweg: Ich kenne mich mit der Bash kaum aus. (bin eigentlich die Eingabeaufforderung/batch von Windows gewöhnt)

Dann sollte das doch kein so großes Problem darstellen :-)

Mein Ziel ist es, ein Verzeichnis per Bash-Datei nach weiteren Verzeichnissen (mit der Syntax "Name_$Nummer") abzusuchen und ein neues Verzeichnis mit der nächsthöheren nicht vorhanden Nummer zu erstellen.

Mein Lösungsvorschlag geht davon aus, dass alle Verzeichnisse, die mit dem Suchmuster übereinstimmen anschließend eine Zahl als Suffix besitzen. Bestimmt geht es auch anders, wahrscheinlich auch eleganter, aber hier mein Vorschlag:

1. Schritt: Suche alle Verzeichnisse, die mit dem Suchmuster übereinstimmen.
2. Schritt: Betrachte davon nur den Zahlanteil nach dem Suchmuster
3. Schritt: Sortiere diese Zahlen
4. Schritt: Betrachte nur die größte Zahl
5. Schritt: Erhöhe die diese Zahl um 1
6. Schritt: Baue den neuen Verzeichnisnamen zusammen: Muster + Zahl
7. Schritt: Erstelle das neue Verzeichnis mit dem ermittelten Namen

1. Schritt:
Während der DIR-Befehl von CMD.EXE und COMMAND.COM einen Schalter besitzt, der das Ergebnis auf Verzeichnisse einschränkt, gibt es meines Wissens kein Gegenstück bei ls. Wir müssen uns mit einem Trick behelfen

ls -d1 beispiel_*/

listet nur die Unterverzeichnisse im aktuellen Verzeichnis aus, die mit beispiel_ beginnen.

-d sorgt dafür, dass im aktuellen Verzeichnis geblieben wird
  -1 damit jeder Eintrag eine Zeile einnimmt.
  */, weil Verzeichnisse auf / enden :-)

2. Schritt:
Betrachte nur den Zahlenanteil in diesen Verzeichnisnamen. Dazu sind zwei Teilschritte erforderlich: Abschneiden des Präfixes "beispiel_" und Abschneiden des Slashes am Ende des Verzeichnisnamens. Für beides können wir das Kommando

cut

verwenden: Mit

ls -d1 beispiel_*/ | cut -d '/' -f 1

reichen wir die Ausgabe weiter an cut.

-d '/' sorgt dafür, dass am Zeichen '/' aufgetrennt wird. Dieses ist ein in Dateinamen verbotenes Zeichen, es kann somit nur am Ende vorkommen.
-f 1 besagt, dass der erste Abschnitt genommen wird, das ist der vor dem Slash am Ende.

Nun müssen wir noch das Präfix abschneiden. "beispiel_" hat 9 Buchstaben, also nehmen wir nur alles ab dem 10. Zeichen:

ls -d1 beispiel_*/ | cut -d '/' -f 1 | cut -c 10-

Wir schicken einfach das Ergebnis wieder an cut und übergeben den Schalter und Parameter, um alles ab dem 10. Zeichen zu bekommen.

3. Schritt: Sortieren
Zum Sortieren schicken wir das alles an das Kommando sort

ls -d1 beispiel_*/ | cut -d '/' -f 1 | cut -c 10- | sort -g

Der Schalter g sorgt für numerische Sortierung, genau das was wir wollen. Die höchste Zahl steht in der letzten Zeile.

4. Schritt: Größte Zahl
Die letzte Zeile bekommen wir mit tail

ls -d1 beispiel_*/ | cut -d '/' -f 1 | cut -c 10- | sort -g | tail -n 1

Mit dem Schalter n kann man die Zahl der angezeigten Zeilen festlegen. Wir brauchen nur die letzte.

5. Schritt: Erhöhen um 1
Den Wert, den der Befehl zurückliefert bekommen wir über $(Befehl) und wenden auf diesen Shell-Arithmetik an, siehe auch dieses Archivposting.

echo $[1+$(ls -d1 beispiel_*/ | cut -d '/' -f 1 | cut -c 10- | sort -g | tail -n 1)]

gibt die nächstgrößere Zahl aus

6. Schritt: Neuer Verzeichnisname

echo "beispiel_"$[1+$(ls -d1 beispiel_*/ | cut -d '/' -f 1 | cut -c 10- | sort -g | tail -n 1)]

gibt den neuen Verzeichnisnamen aus.

Wenn wir das ganze nun in ein Shellskript packen, können wir mit Variablen arbeiten, die Länge der Zeichenkette bestimmen und damit das ganze flexibler halten:

  
#! /bin/bash  
  
MUSTER='beispiel_'   # Weise der Variablen MUSTER die Zeichenkette zu,  
                     # die den Anfang des Dateinamens bildet.  
ANZAHL=${#MUSTER}    # In der Variablen ANZAHL steht nun die Anzahl der  
                     # Zeichen der Zeichenkette in der Variablen MUSTER  
                     # d.h. die Stringlänge von MUSTER  
TRENNER=$[$ANZAHL+1] # Wir benötigen die Zeichen _nach_ MUSTER  
  
# Ermittle die höchste als Suffix vergebene Zahl in den Verzeichnisnamen  
ZAHL=$(ls -d1 ${MUSTER}*/ | cut -d '/' -f 1 | cut -c ${TRENNER}- | sort -g | tail -n 1)  
ZAHL=$[1+$ZAHL]      # Ermittle die nächsthöhere Zahl  
VERZEICHNIS=$MUSTER$ZAHL # Ermittle den neuen Verzeichnisnamen  
mkdir $VERZEICHNIS   # Erstelle das neue Verzeichnis  

Nun kann man die meisten einzelnen Anweisungen einsparen, der Code wird dadurch weder schöner noch übersichtlicher - und Geschwindigkeit ist sowieso kein Thema:

  
#! /bin/bash  
  
MUSTER='beispiel_'   # Weise der Variablen MUSTER die Zeichenkette zu,  
                     # die den Anfang des Dateinamens bildet.  
  
# Mit dem Backslash am Ende einer Zeile kann man eine Anweisung auf  
# mehrere Zeilen verteilen ...  
  
mkdir $MUSTER$[1+$(ls -d1 ${MUSTER}*/ \  
| cut -d '/' -f 1 \  
| cut -c $[1+${#MUSTER}]- \  
| sort -g \  
| tail -n 1)]  

sollte es tun. Getestet mit GNU bash, version 3.1.17(1)-release.
(Abtippfehler sind möglich, copy & paste war nicht möglich ...)

Du solltest sehen, wo Du eventuell eine Parameterübergabe einbauen könntest,
Du solltest verstehen, dass das Skript derzeit nur in dem Verzeichnis funktioniert, in dem gesucht werden soll.
Du solltest es mit einer Fehlerbehandlung ausstatten und an Deine Bedürfnisse anpassen und so abändern, dass Du es in Deinem persönliches bin-Verzeichnis abspeichern kannst.

Dabei wünsche ich Dir viel Erfolg.
Verbesserungsvorschläge werden gerne entgegengenommen.

Freundliche Grüße

Vinzenz

[1] Arthur Bloch, Murphys Gesetze, Die Regel des Mars