[linux-l] Einfaches Shellscript zum Umbenennen vieler Dateien gesucht

Lutz Willek lutz.willek at belug.de
Do Mai 16 08:16:37 CEST 2019


Hi Norman,

Danke, das ist eine wirklich sehr schöne Frage: genau richtig um sie 
morgens beim ersten Kaffee zu beantworten.

Erst mal sehen ob ich Dich richtig verstanden habe. Ich baue Dein 
Szenario mal nach.

Ausgangsstellung:

➜  ~ mkdir test
➜  ~ cd test
➜  for d in a b c d e f g ; do mkdir -p $d ; cd $d ; for f in $(seq 100 
105) ; do touch ${f}_ ; done ; touch datei_ohne_am_ende ; cd .. ; done
➜  for f in $(seq 100 105) ; do touch ${f}_ ; done; touch 
datei_ohne_am_ende;

Resultat: (Ausgabe gekürzt)

➜  find . -type f
./105_
./datei_ohne_am_ende
./g/105_
./g/datei_ohne_am_ende
./g/102_
./g/101_
./g/100_
./g/103_
./g/104_
./102_
./a/105_
./a/datei_ohne_am_ende
./a/102_
./a/101_
... (schnipp)...

Das entspricht in etwa Deiner jetzigen Situation? Wenn  ja, dann würde ich:

- hier einfach mit einem "find" Befehl anfangen, um alle Dateien zu 
filtern die mit einem Unterstrich enden (find . -type f -name '*_')

- dann die Ausgabe als Variable zeilenweise in eine Schleife legen 
(nicht unbedingt nötig und unperformant, erleichtert aber meine 
Erklärung ungemein)

- dann den Unterstrich entfernen und in eine zweite Variable packen

- dann einfach mit "mv" die Datei umbenennen.

Dieser Befehl sieht dann in etwa so aus: (hier wird nicht wirklich 
umbenannt, sondern die Variablen werden nur angezeigt)

➜  find . -type f -name '*_' | while read file_ ; do file=$(echo $file_ 
| sed 's/_$//') ; echo Umbenennen von "$file_" nach "$file" ; done

Das Ergebnis (stark gekürzt) dürfte dann in etwa so aussehen:

Umbenennen von ./105_ nach ./105
Umbenennen von ./g/105_ nach ./g/105
Umbenennen von ./g/102_ nach ./g/102
Umbenennen von ./g/101_ nach ./g/101
Umbenennen von ./g/100_ nach ./g/100
... (schnipp)...

Falls Du die verwendeten Befehle nicht kennst und das Beispiel 
nachvollziehen möchtest, dann schaue bitte in die jeweilig man page. Das 
geht auf der Kommandozeile mit dem Befehl "man find" (hier um die manual 
Seite für den Befehl "find" anzuzeigen), oder Du schaust einfach online 
in den manpages nach:

http://man7.org/linux/man-pages/man1/find.1.html

http://man7.org/linux/man-pages/man1/bash.1.html

http://man7.org/linux/man-pages/man1/sed.1.html

http://man7.org/linux/man-pages/man1/echo.1.html

http://man7.org/linux/man-pages/man1/mv.1.html

Als sehr hilfreich empfinde ich übrigens die Webseite 
https://explainshell.com/, da Du hier die Möglichkeit hast Dir alles auf 
einen Blick anzusehen. Solltest Du diese Seite nicht kennen: Sie ist 
meiner Meinung nach einen Blick wert. Der Beispielbefehl von oben wird 
in dieser URL erklärt:

https://explainshell.com/explain?cmd=find+.+-type+f+-name+%27*_%27+%7C+while+read+file_+%3B+do+file%3D%24%28echo+%24file_+%7C+sed+%27s%2F_%24%2F%2F%27%29+%3B+echo+Umbenennen+von+%22%24file_%22+nach+%22%24file%22+%3B+done


Soweit die Theorie. In der Praxis würde ich den Befehl so nicht wirklich 
verwenden, da er zwar alles schön erklärt, jedoch recht anfällig für 
Fehler ist, und bei vielen 100.000 Dateien auch sehr sehr langsam. Aber 
er erklärt schön das Grundprinzip von Linux-Befehlen: Einzelne kleine 
und spezialisierte Befehle werden genutzt und dann mit einer Pipe (|) 
kombiniert um eine Aufgabe zu erledigen.

In der Praxis würde ich beispielsweise noch dafür Sorge tragen das auch 
Dateien mit Sonderzeichen korrekt behandelt werden, und auch etwas die 
Performance optimieren indem ich einen speziellen Typ Schleife benutze, 
der die paralelle Mehrfachausführung zulässt. Die Grundidee bleibt dabei 
genau die gleiche, es ist jedoch nicht mehr ganz so einfach zu verstehen.

Um auch wirklich alle Sonderzeichen in den Dateinamen zu erwischen würde 
ich wohl mit "find ... -print0" arbeiten.
Anstatt einer "while .. do .. done" Schleife würde ich in der Praxis 
wohl eher mit einem "xargs" arbeiten. Als Alternative bietet sich auch 
das "-exec" von find an.
Die Ersetzung mittels "sed" würde ich persönlich beibehalten, jedoch 
könnte man hier auch andere Befehle wie awk oder inline shell 
variablenersetzung verwenden.

Einschub: An dieser Stelle gibt es wirklich sehr viele Möglichkeiten zur 
Optimierung, und ich bin mir ziemlich sicher das die "alten Hasen" hier 
auf der Liste jetzt so ein zucken in den Fingern spüren, die Aufgabe 
noch viel eleganter zu erledigen als ich. :-)  --> Immer her mit 
Alternativen! Eine bessere Steilvorlage bekommt ihr nie mehr!

Mein Endergebnis würde dann in etwa so aussehen: (dieser Befehl zeigt 
wieder nur an anstatt wirklich Dateien umzubenennen)

➜  find . -type f -name '*_' -print0 | xargs --null -I {} sh -c "echo 
Umbenennen von {} nach {}" | sed 's/_$//'

Das Ergebnis (stark gekürzt) ist identisch zur ersten Ausgabe:

Umbenennen von ./105_ nach ./105
Umbenennen von ./g/105_ nach ./g/105
Umbenennen von ./g/102_ nach ./g/102
Umbenennen von ./g/101_ nach ./g/101
Umbenennen von ./g/100_ nach ./g/100
... (schnipp)...


Um die Dateien dann auch wirklich umzubenennen wäre der endgültige 
Befehl also: (Vorsicht: Dieser Befehl ändert wirklich Dateinamen!)

➜  find . -type f -name '*_' -print0 | xargs -0 -I {} echo mv "{}" "{}" 
| sed 's/_$//' | sh

Siehe zur Erklärung: 
https://explainshell.com/explain?cmd=+find+.+-type+f+-name+%27*_%27+-print0+%7C+xargs+-0+-I+%7B%7D+echo+mv+%22%7B%7D%22+%22%7B%7D%22+%7C+sed+%27s%2F_%24%2F%2F%27+%7C+sh

Das Endergebnis sieht dann so aus: (wieder stark gekürzt)

➜  find .
.
./104
./103
./102
./105
./datei_ohne_am_ende
./g
./g/104
./g/103

... (schnipp)...

Hoffe das hilft Dir weiter.

Liebe Grüße Lutz


On 16.05.19 01:02, Norman Steinbach wrote:
> Hallo Linux-Liste,
>
> ich suche ein Shellscript, welches viele Dateien in vielen 
> Unterverzeichnissen rekursiv jeweils so umbenennt, dass das letzte 
> Zeichen der Dateiendung gelöscht wird. Dieses Zeichen ist immer ein 
> Unterstrich "_", und der soll weg.
>
> Hintergrund: Ich sichere gerade von einem übervollen Samsung-Handy 
> Daten auf eine externe Festplatte. Das klappte per MTP nicht so, wie 
> es sollte, weil jeweils zu lange gebraucht wird, um den 
> Verzeichnisinhalt im Gerätespeicher einzulesen. Der Workaround ist 
> gewesen, per "SmartSwitch" die Dateien (in erster Linie Tausende von 
> Bildern) auf die SD-Karte zu sichern, von wo sie sich dann problemlos 
> kopieren lassen.
> Diese Backup-Äpp benennt die Dateien jedoch immer so um, dass sie an 
> den jeweiligen Dateinamen einen Unterstrich dran hängt.
>
> Da die Daten jedoch nicht als Backup in das Handy zurückgespielt 
> werden sollen, sondern vom PC aus (unter Windows - das hat es 
> überhaupt nicht hingekriegt mit der Sicherung) genutzt werden sollen, 
> müssen die Dateinamen, um nun wieder "verwertbar" zu sein, alle wieder 
> in ihr ordentliches Format gebracht werden, d.h. "dateiname.jpg_" muss 
> wieder zu "dateiname.jpg" werden, bei allen anderen Endungen analog 
> (z.B. ".mp4_" bei Videos zu ".mp4", oder ".pdf_" zu ".pdf" usw.)
>
> Hierfür gibt es sicherlich eine schöne, einfache Methode, das per 
> Shell-Script zu automatisieren - nur dass ich leider nie gelernt habe, 
> Shell-Scripte selbst zu entwickeln, weshalb ich hier um Hilfe bitten 
> möchte.
>
> Falls das mit der Rekursion in die Unterverzeichnisse nicht 
> funktioniert, wäre auch eine Variante "im aktuellen Verzeichnis" 
> ausreichend, da nicht so viele davon relevante Daten enthalten, und 
> der Rest eh wegfliegt.
>
>
> Danke & viele Grüße,
>
> Norman
> _______________________________________________
> linux-l mailing list
> linux-l at mlists.in-berlin.de
> Die Mailingliste der BeLUG (Berliner Linux User Group)
>
> Wenn du diese Mailingliste  abbestellen willst, gehe bitte auf
> https://mlists.in-berlin.de/mailman/listinfo/linux-l-mlists.in-berlin.de
> und trage dich dort bitte aus
-- 
Lutz Willek <https://de.linkedin.com/in/lutzwillek/en?trk=profile-badge>



Mehr Informationen über die Mailingliste linux-l