So benennen Sie mehrere Dateien mit find um

37

Ich möchte mehrere Dateien (file1 ... filen to file1_renamed ... filen_renamed) mit folgendem Befehl umbenennen find:

find . -type f -name 'file*' -exec mv filename='{}' $(basename $filename)_renamed ';'

Aber diesen Fehler bekommen:

mv: cannot stat filename=./file1’: No such file or directory

Dies funktioniert nicht, da der Dateiname nicht als Shell-Variable interpretiert wird.

user164014
quelle

Antworten:

46

Das Folgende ist eine direkte Lösung Ihres Ansatzes:

find . -type f -name 'file*' -exec sh -c 'x="{}"; mv "$x" "${x}_renamed"' \;

Dies ist jedoch sehr teuer, wenn Sie viele übereinstimmende Dateien haben, da Sie mvfür jede Übereinstimmung eine neue Shell starten (die a ausführt ). Und wenn Sie lustige Zeichen in einem Dateinamen haben, wird dies explodieren. Ein effizienterer und sichererer Ansatz ist der folgende:

find . -type f -name 'file*' -print0 | xargs --null -I{} mv {} {}_renamed

Es hat auch den Vorteil, mit seltsam benannten Dateien zu arbeiten. Wenn es findunterstützt wird, kann dies auf reduziert werden

find . -type f -name 'file*' -exec mv {} {}_renamed \;

Die xargsVersion ist nützlich, wenn Sie nicht verwenden {}, wie in

find .... -print0 | xargs --null rm

Hier rmwird einmalig (oder mit vielen Dateien mehrmals) aufgerufen, aber nicht für jede Datei.

Ich habe die basenamein dir stehende Frage entfernt, weil es wahrscheinlich falsch ist: Du würdest foo/bar/file8zu file8_renamed, nicht foo/bar/file8_renamed.

Bearbeitungen (wie in den Kommentaren vorgeschlagen):

  • findOhne gekürzt hinzugefügtxargs
  • Sicherheitsaufkleber hinzugefügt
Thomas Erker
quelle
In dem Fall xist das nutzlos: find . -type f -name 'file*' -exec mv {} "{}_renamed" \; xargsVersion haben die gleiche Effizienz wie das erste Beispiel /
Costas
Das xgibt es nur, um die Ansprache des Fragestellers direkt zu beheben.
Thomas Erker
2
(1) Die {}direkte Verwendung in einem shell ( sh -c "…") -Befehl ist sehr gefährlich - Sie sollten ihn immer als Argument übergeben. (2) Nicht alle Versionen findunterstützen das {}_renamedKonstrukt. (3) Ich verstehe Ihre Aussage nicht, xargsdie zum Entfernen von Dateien nützlich ist (im Gegensatz zum Umbenennen).
G-Man sagt "Monica wiedereinstellen"
@ G-Man: Der Unterschied mit xargsist nicht mvvs. rm, sondern mit {}vs. ohne. Ersteres ähneltmv file1 file1_renamed; mv file2 file2_renamed dem letzteren rm file1 file2.
Thomas Erker
1
und wenn Sie dann die _renamed-Dateien wieder auf ihren ursprünglichen Namen ändern wollten, wie würden Sie das tun?
mcmillab
17

Nachdem ich die erste Antwort ausprobiert und ein wenig damit gespielt hatte, stellte ich fest, dass dies etwas kürzer und weniger komplex sein kann -execdir:

find . -type f -name 'file*' -execdir mv {} {}_renamed ';'

Sieht so aus, als sollte es auch genau das tun, was Sie brauchen.

Matijs
quelle
2
Mit findImplementierungen, die dies unterstützen -execdirund {}nicht als Ganzes, ist dies auch die sicherste. Sie können eine hinzufügen möchten -izu , mvobwohl (und -Twenn Ihr mvunterstützt)
Stéphane Chazelas
1
@ StéphaneChazelas noch besser, anstatt sich auf mveine Eingabeaufforderung zu verlassen, oder zusätzlich dazu können Sie (ohne Zweifel abhängig davon, ob Ihre Implementierung dies findunterstützt) auch verwenden, -okdirwelches den auszuführenden Befehl vor der Ausführung ausgibt.
Matijs
Erwähnenswert ist, dass dies -depthauch eine gute Idee ist, wenn Sie auch Verzeichnisnamen berühren.
Ciro Santilli am
Argh, nur festgestellt, dass -execdirhat eine sehr ärgerliche Kehrseite, findweigert sich, etwas zu tun, wenn PATHrelative Pfade enthält ... askubuntu.com/questions/621132/… find: The relative path XXX is included in the PATH environment
Ciro Santilli am
6

Ein anderer Ansatz ist die Verwendung einer while readSchleife über dem findAusgang. Dies ermöglicht den Zugriff auf jeden Dateinamen als Variable, die bearbeitet werden kann, ohne sich um zusätzliche Kosten / potenzielle Sicherheitsprobleme beim Erstellen eines separaten sh -cProzesses mit kümmern zu müssenfind der -execOption von erstellt wird.

find . -type f -name 'file*' |
    while IFS= read file_name; do
        mv "$file_name" "${file_name##*\/}_renamed"
    done

Und wenn die verwendete Shell die -dOption zur Angabe eines readBegrenzers unterstützt, können Sie Dateien mit seltsamen Namen (z. B. mit einem Zeilenumbruch) folgendermaßen unterstützen:

find . -type f -name 'file*' -print0 |
    while IFS= read -d '' file_name; do
        mv "$file_name" "${file_name##*\/}_renamed"
    done
John B
quelle
3

Ich möchte auf die erste Antwort eingehen und beachten, dass dies beim Anhängen an den Dateinamen nicht funktioniert, da das Pfadpräfix im Dateinamenargument ./vorhanden ist.

Wenn ich die Antwort von Thomas Erker ändere, finde ich, dass dies ein allgemeinerer Ansatz ist

find . -name PATTERN -printf "%f\0" | xargs --null -I{} mv {} "prefix {} suffix"

Optionen für xargs:

--nullGibt an, dass jedes übergebene Argument stdinmit einem Nullzeichen ( \0) endet . Auf diese Weise kann der Dateiname Leerzeichen enthalten, andernfalls wird jedes Wort als ein anderer Parameter für die bedrohtmv Befehl .

-I replace-strJedes Auftreten von replace-strwird durch das Argument von ersetzt stdin. Sie können es also für eine andere Zeichenfolge ändern, wenn Sie dies benötigen.

Sdlion
quelle
Warum machen die pringf "%f\0"statt a print0?
SLM
1
@sim, weil Präfixe -print0erzeugt ./werden, wenn es PATTERNShell-Metazeichen enthält, die beim Umbenennen in etwas stören, das den ursprünglichen Namen vorangestellt ist . (zB umbenennen 0 - foo.txtin 00 - foo.txt, 1 - bar.txtnach 01 - bar.txtusw.)
das-g
2

Ich konnte etwas ähnliches mit dem tun for, findund mv.

for i in $(find . -name 'config.yml'); do mv $i $i.bak; done

Dadurch werden alle config.ymlDateien gefunden und in umbenanntconfig.yml.bak

Varun Risbud
quelle
Dies hat den Vorteil, dass eine variable Ausdehnung verwendet werden kann, z. $ {i: r}. Gute Antwort.
Salami
Ja, Sie können so ziemlich jeden Ausdruck in das foreinfügen und eine Massenoperation ausführen.
Varun Risbud