[BASH] Bulkrename mit dem Terminal

  • Die [Bash] und ihre skurrilen Oneliner (Bulkrename im Terminal)

    da wir ja kürzlich erst das thema mit der umbenennung von mehreren dateien mittels GUI hatten. hier mal ein beispiel, wie sich das problem auch mit dem terminal und der bash lösen lässt. für meine lösung habe ich zur veranschaulichung einen ordner 'test' angelegt und dort eine kleine anzahl an bildern abgelegt, die ich nach und nach mit dem wallpaper-changer 'variety' in meinem bilder-archiv gesammelt habe. die einzelnen dateinamen sind dabei so geblieben, wie sie 'variety' vor langer zeit mal aus dem netz gezogen hat

    nachdem ich ein terminal in diesem ordner geöffnet habe. lege ich zu sicherheit erstmal ein backup der dateien an, damit ich ungewollte veränderungen wieder rückgängig machen kann. war/ist es nicht staryvyr der immer wieder darauf hinweist, 'Kein Backup Kein Mitleid' und er hat recht, backups kann man nicht genug haben. gerade wer im terminal mit aufwendigen befehlskonstrukten arbeitet sollte das beherzigen, denn hin und wieder kann auch mal was schiefgehen. die wahrscheinlichkeit wird zwar mit der steigenden erfahrung immer geringer, aber trotzdem!

    als erstes kommt also das backup dran.

    tar -cf ~/pic.tar *

    mit diesem befehl lege ich ein archiv 'pic.tar' in meinem home-verzeichnis an. darin sind dann alle dateien enthalten die 'tar' mittels '*' finden konnte. '*' steht für alle dateien in diesem ordner. die option '-cf' ist die abgekürzte form für '-c' = compress und '-f' = filename in meinem fall der name des archivs 'pic.tar' inklusive pfadangabe '~/' (steht für homeverzeichnis) wo die datei später liegen soll .

    da wir das backup 'pic.tar' nur temporär bis gar nicht brauchen, finde ich diese art des backups recht sinnvoll. man könnte natürlich eine kopie des ordners 'test' irgendwo anders auf der festplatte ablegen. der aufwand dafür, gerade bei mehreren hundert dateien, ist dann aber schon deutlich grösser. erst recht, wenn man nach erfolgreichem abschluss alles wieder löschen will. das geht mit dem archiv und nur einer datei, die gelöscht werden muss, eben auch recht fix.

    im zweiten schritt liste ich erstmal alle dateien auf, um zu schauen, wie die einzelnen dateinamen überhaupt aussehen. dabei aber schon gleich mit einem netten feature der bash. indem ich zwei befehle in einem ausführe. zum einen verwende ich den befehl 'ls' zum auflisten der datei-einträge. im gleichen zug gebe ich diese liste mittels '|' pipe-operator an ein weiteren befehl 'cat' (concatenate) weiter. die eigentliche aufgabe von 'cat' ist es, einzelne dateien zu einer datei zusammenzufügen. also in etwa der art 'cat file_A file_B file_C > newfile'. 'cat' bietet aber auch die hilfreiche option '-n' = numbering. wenn ich diese option für 'cat' mit angebe, wird die ausgabe von 'ls' zusätzlich fortlaufend nummeriert. das hat für meine aufgabenstellung mehrere vorteile. zum einen sehe ich die anzahl der dateien, wieviele bilder also später umbenannt werden. zum anderen habe ich gleich zwei kriterien für die umbennung, den alten dateinamen und eine fortlaufende nummer für die neuen dateinamen.

    ls | cat -n die ausgabe im terminal sieht dann wie folgt aus:


    im nächsten schritt, wird diese befehlszeile dann noch um eine 'while; do' bedingung erweitert. ich nähere mich damit der eigentlichen umbennung der dateien. hierbei nutze ich die ausgabe von 'cat' um sie in einer 'while'-schleife mittels 'read' einzulesen, um sie dann wiederum aber dieses mal mit dem befehl 'echo' auszugeben.

    für 'read' definiere ich nun zwei variablen. als erstes 'n' für number und 'f' für filename. die wahl der variablen für 'read' ist willkürlich aber passend zum thema gewählt. ich könnte für n,f auch einfach x,y definieren, das würde auch funktionieren. die erweiterte befehls-zeile sieht damit wie folgt aus:

    ls | cat -n | while read n f; do echo $f $n; done

    'ls' liest die einträge ein, 'cat' erzeugt zusätzlich eine fortlaufende nummer und gibt sie am anfang jeder zeile mit an. 'read' übernimmt von 'cat' dessen ausgabe und unterteilt sie in jeweils zwei felder (variablen) nummer und dateiname. am ende nimmt 'echo' diese beide variablen $n,$f und gibt sie dann im terminal aus. über die definition als 'while'-schleife wird das ganze solange gemacht bis 'ls' alle dateinamen ausgegeben hat.

    mit dem befehl 'echo' ist die ausgabe der variablen $f,$n auch schon passend für die spätere umbennung vertauscht. ($f) = alter dateiname, ($n) = neuer dateiname.

    damit bin ich meinem ziel wieder ein stück näher gerückt. allerdings fehlt für die umbennung noch das wesentliche. bisher existiert nur eine fortlaufende nummer. ein plausibler name und eine aussagekräftige extension, um was für ein dateiformat es sich bei den einzelnen dateien handelt, fehlt noch. das kommt im nächsten schritt. ich ergänze die befehlszeile noch ein weiteres mal:

    ls | cat -n | while read n f; do echo $f "Wallpaper_"$n"."${f##*.}; done

    einmal setze ich direkt vor die variable $n, also ohne leerzeichen dazwischen, den string "Wallpaper_" und zusätzlich noch den string "." direkt hinter die variable $n. das ist dann der delimeter für die extension, die nun auch noch folgt. ${f##*.}

    ${f##*.} ist eine sogenannte 'brace/parameter expansion' der bash. in unserer sprache heisst das: lösche alles aus der variable $f bis zum letzten punkt (inklusive punkt) im beispiel von nr. 18 : "IC405_Abolfath_3171. jpg"

    dabei ist 'f' die variable, die read erzeugt hat, also der dateiname. die beiden '##' rautezeichen (neu-deutsch hashtag) geben vor, das der bash-interpreter vom anfang des strings ($f) jedes beliebige zeichen '*' bis zum zeichen das einem '.' entspricht gelöscht werden soll. kommt der 'punkt' mehrmals im string vor, werden diese ebenfalls bis einschiesslich zum letzten '.' gelöscht. damit ist sichergestellt, das am ende wirklich nur die extension (jpg, png, etc..) vom string '$f' übrig bleibt.

    lasse ich in der parameter erweiterung eine raute '#' weg und schreibe nur ${#*.}, würde in meinem beispiel, die umbennung in vielen fällen ebenfalls funktionieren. allerdings wird dann nur bis zum und einschliesslich dem ersten '.' punkt alles gelöscht. das wäre z. bsp. für eintrag nr. 1 "169A. 2911.Dai.jpg" eine unerwünschte ausgabe. da kommt auch gleich nochmal der fingerzeig von staryvyr "Kein Backup, Kein Mitleid" ins spiel. beim tippen von lnagen bfeehlszylen schlaischt siech schnll n felhler eni und im rausch der sinne, hat man mal wieder viel zu schnell [enter] gedrückt. shohn isst dass kaos uf der fehstpladde perrffekt! - voilà, viel spass beim aufräumen!

    hier ein kleiner exkurs - how we can use it, our brace expansion!

    ${f##*.} trim anything upto the last char '.' from the head of string $f

    ${f#*.} trim anything upto the first char '.' from the head of string $f

    ${f%.*} trim anything upto the first char '.' from the tail of string $f

    ${f%%.*} trim anything upto the last char '.' from the tail of string $f

    die parameter-erweiterung ${##*.} gibt mir also die möglichkeit, die ursprüngliche extension des alten dateinamens für den neuen einfach zu übernehmen. die ausführung der befehlszeile sieht nun wie folgt aus:

    eigentlich habe ich mein ziel erreicht. denn die ausgabe sieht jetzt (fast) so aus, wie ich es mir für die umbennung vorgestellt habe. im letzten schritt, werde ich einfach den befehl 'echo' durch 'mv' (move) ersetzen und damit bei ausführung letztendlich alle dateien in diesem ordner umbennen.

    ls | cat -n | while read n f; do mv $f "Wallpaper_"$n"."${f##*.}; done

    bevor ich aber 'echo' mit 'mv' ersetze gebe erst noch einmal aus, wie es nach der umbennung aussehen würde.

    wie man sieht, sind die ersten neun einträge aufgund fehlender nullen etwas eingerückt. ich kann das mit meiner lasziven ästhetik für schöne dinge nur schwer vereinbaren und möchte auch hier wieder mein allseits beliebtes zero-padding einbringen. dafür braucht es aber eine weitere variable, ich nenne sie einfach 'z'. diese variable muss nun zusätzlich noch in meinen schon etwas längeren 'oneliner' untergebracht werden.

    z=000; ls | cat -n | while read n f; do echo $f "wallpaper_"${z:${#n}}${n}"."${f##*.}; done

    am beginn der zeile definiere ich erstmal 'z' und weise ihr '000' zu, damit weiss die bash um was es sich bei 'z' handelt, wenn sie beim nachfolgenden interpretieren der befehls-zeile auf diese variable trifft.

    die variable $n habe ich mittels brace/parameter-erweiterung ${z:${#n}}${n} entsprechend angepasst und die variable ($z) für das zero-padding mit eingebunden. dadurch werden alle zahlen bezogen auf 'z', die <100 sind mit nullen aufgefüllt. für mein archiv mit derzeit ca. 450 bildern reicht das aus. die variable 'z' lässt sich aber je nach anzahl der gewünschten stellen auch einfach erweitern z.bsp. 'z=000000' für 6 stellige werte etc...

    die brace-expansion ${z:${#n}}${n} wird dabei wie folgt interpretiert:

    ${n} steht für die ausgabe der variable $n, also 1,2,3,...25,26,27

    ${#n} steht für die länge (anzahl zeichen) von $n. für 1-9 = 1 zeichen, für 10-27 = 2 zeichen

    ${z:${#n}} sagt jetzt nicht anderes als: gib die variable $z = 000 ab dem zeichen (n) aus. für die nummerierung von 1-9 und n = 1 wird der string von '$z=000' also zu "00" und ab 10 aufwärts zu "0".

    n=1-9 -> ${z:${#n}}${n} = ${000:{1}}{n} = ${00}{n} = 001,002,003,...,008,009

    n=10-27 -> ${z:${#n}}${n} = ${000:{2}}{n} = ${0}{n} = 010,011,012,...,026,027

    hier nochmal eine ausgabe für dreistellige werte in der nummerierung.

    und zum schluss die entgültige befehlszeile, ein noch relativ harmlos daherkommender 'oneliner'! wobei 'echo' nun auch durch 'mv' ersetzt wurde.

    z=000; ls | cat -n | while read n f; do mv $f "wallpaper_"${z:${#n}}${n}"."${f##*.}; done

    rijo...

    PS: don't blame me, if it doesn't work for you and Kill Yourself With Pain!

    33 Mal editiert, zuletzt von rijo (24. Oktober 2023 um 18:23) aus folgendem Grund: shraibfhleer besytigt.

  • Hallo,

    das ist ein ganz tolles Beispiel für's pipen von Befehlen, wildcard * nutzung und

    counting # mit Dateien innerhalb einer Linux Shell.

    Eine gute Demonstration, wie mächtig diese Werkbank ist.

    Und dann auch noch richtig gut erklärt und beschrieben.

    Hut Ab rijo, :) vielen Dank, für die Arbeit. :thumbup:

    Spoiler anzeigen

  • A.E. - thanks a lot! vielleicht gibt es bei zeiten noch ein paar beispiele von mir. ist allerdings eine frage der zeit. das tippt man nicht einfach so mal eben. hab mir ein paar abende erstmal einige gedanken dazu gemacht, wie sich das am besten umsetzen lässt.

    rijo...

  • rijo ich weiss was Du meinst. Auch wenn man selbst Versteht wie es geht und mit solchen Sachen

    weitesgehend für seine Zwecke umgehen kann, kommt ja noch oben drauf, es verständlich zu Erklären.

    Und dann schriftlich hier verständlich darzustellen. Auf jeden Fall: Danke :)

    Spoiler anzeigen

Jetzt mitmachen!

Sie haben noch kein Benutzerkonto auf unserer Seite? Registrieren Sie sich kostenlos und nehmen Sie an unserer Community teil!