Da hatte ich gerade ein Problem mit dem umbenennen von mehreren Dateien. Diese enthielten im Namen das Datum in der Form backup-TTMMJJJJ.bz2. Ich wollte es aber gerne andersrum haben, also backup-JJJJMMTT.bz2, damit sich diese Dateien auch richtig sortieren lassen.
Ungern wollte ich alle Dateien von Hand umbenennen. Also habe ich schnell ein Shell Skript geschrieben, dass die Aufgabe lösen sollte. Mit Hilfe von ein wenig sed und awk ließ sich das auch ganz gut umsetzen.
#!/bin/sh --
for i in *.bz2;
do
olddate=`basename "$i" .bz2|awk -F- '{ print $2}'`
newdate=`echo $olddate| sed -e 's/\([0-9]\{2\}\)\([0-9]\{2\}\)\([0-9]\{4\}\)/\3\2\1/g'`
prefix=`echo $olddate|tr -d [:digit:]`
mv "$i" ${prefix}${newdate}.bz2
done
Das Skript nimmt alle bz2 Dateien im aktuellen Verzeichnis, und benennt diese von der Form backup-TTMMJJJJ.bz2 in backup-JJJJMMTT.bz2 um.
Was tun nun die einzelnen Zeilen?
for i in *.bz2
für jede .bz2 Datei im aktuellen Verzeichnis tue...
olddate=`basename $i .bz2|awk -F- '{ print $2}'`
speichere das alte Datumsformat in der Variablen olddate. Hierzu wird die Funktion basename genutzt, die aus einem übergebenem Dateinamen mit Pfad den Pfad, sowie einen evtuell vorhandenen Suffix entfernt. Also wird an die Pipe eine Zeichenkette in der Form backup-TTMMJJJJ übegeben.
Awk übernimmt die Zeichenkette. Mit -F wird das Trennzeichen für die Felder angegeben. In diesem Fall "-", weil ich nur das Datum erhalten wollte, und gibt das Datum zurück, das im zweiten Feld gespeichert ist.
newdate=`echo $olddate| sed -e 's/\([0-9]\{2\}\)\([0-9]\{2\}\)\([0-9]\{4\}\)/\3\2\1/g'`
Jetzt wird es interessant. Um das neue Datumsformat zu erhalten, wird das alte Datumsformat genommen und an sed übergeben. Sed durchsucht die übergebene Zeichenkette nach dem definierten regulären Ausdruck1 und ersetzt diesen durch den zweiten spezifizierten.
Die allgemeine Form für Suchen und Ersetzen lautet dabei: s/Ausdruck1/Ausdruck2/Optionen.
Ausdruck1 hat dabei die folgende Form:
\([0-9]\{2\}\)\([0-9]\{2\}\)\([0-9]\{4\}\)
[0-9] eine sogenannte Character Klasse. Hier wird eine Zahl definiert.
\{2\} Der vorhergende Ausdruck darf genau zweimal auftauchen.
\( \) Der Ausdruck in den Klammern wird gemerkt.
Das macht also aus:
\([0-9]\{2\}\) Suche eine Zahl, die genau 2 mal auftritt und merke dir diese (entspricht den Tagen).
Danach geht es in der selben Form weiter:
\([0-9]\{2\}\) Das ganze nochmal (entspricht den Monaten).
\([0-9]\{4\}\) Und jetzt kommen noch 4 Zahlen (entspricht dem Jahr).
Diese Ausdrücke sollen jetzt in einer anderen Form wieder genutzt werden (deshalb das Merken der Ausdrücke):
\3\2\1 nimm die gefundenen Ausdrücke und ersetze sie in der umgekehrten Reihenfolge. Mit \1 wird auf das erste \(\) Klammernpaar Bezug genommen, mit \2 auf das zweite, und so weiter.
Als letztes noch die Option "g". Die besagt, dass global über den ganzen übergebenen Text gesucht und ersetzt werden soll. Hier ist es eigentlich unnötig. Das hab ich mir irgendwann mal angewöhnt, und jetzt werde ich es nicht mehr los :-)
Damit haben wir das Datum jetzt in der Form JJJJMMTT.
Jetzt wollen wir noch den Präfix des Dateinamens in einer Variablen speichern. Das macht:
prefix=`echo $olddate|tr -d [:digit:]`
damit wird das alte Datumsformat genommen und durch das "tr" jede auftretende Zahl gelöscht. Damit erhalten wir also als Zeichenkette "backup-".
Jetzt haben wir alle Teile zusammen, und müssen es nur noch in einem mv Aufruf zusammenfassen:
mv "$i" ${prefix}${newdate}.bz2
Damit wird aus dem alten Dateinamen backup-TTMMJJJJ.bz2 backup-JJJJMMTT.bz2
Was mich immer wieder stört, und worüber ich immer wieder stolpere, sind die unterschiedlichen Bedeutungen der einzelnen Zeichen in einem regulären Ausdruck.
Der obige sed-Ausdruck würde nämlich in perl z.B. so aussehen:
perl -pne 's/(\d{4})(\d{2})(\d{2})/\3\2\1/g'`
Hier steht "\d" für ein "digit", also eine Zahl ([0-9] würde aber auch funktionieren).
Man beachte die fehlenden backspaces vor den Klammern. Leider bringe ich das immer wieder durcheinander, wo was erlaubt, und wie auszusehen hat.
Grep, sed, perl, awk, vi(m) alle nutzen reguläre Ausdrücke, aber leider sind sie nicht überall gleich. So kann zum Beispiel awk mit einer bestimmten Wiederholung der vorangegangenen Zeichenklasse nichts anfangen.
Schade eigentlich, aber so werde ich wohl auch in Zukunft weiterhin die entsprechenden Man pages lesen müssen.
gesprochen?
Ich nutze das Program relativ oft, da ich die regulären Ausdrücke von Perl ziemlich praktisch finde :-)
Gruß
Marc
(POSIX 1003.2) und die sind daher gleich; wenn auch
nicht so mächtig (i. Vgl. ext. perl regexps).
Die docu findest Du im POSIX standard, den du bei der
ieee bzw. OpenGroup downloaden kannst.