Wie ändere ich die Erweiterung mehrerer Dateien rekursiv über die Befehlszeile?

73

Ich habe viele Dateien mit der .abcErweiterung und möchte sie ändern in .edefg
Wie mache ich das über die Befehlszeile?

Ich habe einen Stammordner mit vielen Unterordnern, daher sollte die Lösung rekursiv funktionieren.

Tommyk
quelle

Antworten:

85

Ein portabler Weg (der auf jedem POSIX-kompatiblen System funktioniert):

find /the/path -depth -name "*.abc" -exec sh -c 'mv "$1" "${1%.abc}.edefg"' _ {} \;

In bash4 können Sie globstar verwenden, um rekursive Globs zu erhalten (**):

shopt -s globstar
for file in /the/path/**/*.abc; do
  mv "$file" "${file%.abc}.edefg"
done

Der (Perl) rename-Befehl in Ubuntu kann Dateien mithilfe der Perl-Syntax für reguläre Ausdrücke umbenennen, die Sie mit globstar kombinieren können, oder find:

# Using globstar
shopt -s globstar
files=(/the/path/**/*.abc)  

# Best to process the files in chunks to avoid exceeding the maximum argument 
# length. 100 at a time is probably good enough. 
# See http://mywiki.wooledge.org/BashFAQ/095
for ((i = 0; i < ${#files[@]}; i += 100)); do
  rename 's/\.abc$/.edefg/' "${files[@]:i:100}"
done

# Using find:
find /the/path -depth -name "*.abc" -exec rename 's/\.abc$/.edefg/' {} +

Siehe auch http://mywiki.wooledge.org/BashFAQ/030

geirha
quelle
Das erste hängt einfach die neue Erweiterung an die alte an, sie ersetzt nicht ...
user2757729
3
@ user2757729, es ersetzt es. "${1%.abc}"erweitert sich am Ende auf den Wert von $1except without .abc. Weitere Informationen zu Shell-String-Manipulationen finden Sie in FAQ 100 .
Geirha
Ich habe dies auf einer ~ 70k-Datei-Dateistruktur ausgeführt ... zumindest auf meiner Shell hat es diese definitiv nicht ersetzt.
user2757729
1
@ user2757729 Sie haben wahrscheinlich das "abc" in${1%.abc}...
kvnn
1
Falls sich jemand anderes fragt, was der Unterstrich im ersten Befehl tut, ist dies erforderlich, da mit sh -c dem ersten Argument $ 0 zugewiesen wird und alle verbleibenden Argumente den Positionsparametern zugewiesen werden . Das _ist also ein Scheinargument, und '{}' ist das erste Positionsargument für sh -c.
Hellobenallan
48

Dies erledigt die erforderliche Aufgabe, wenn sich alle Dateien in demselben Ordner befinden

rename 's/.abc$/.edefg/' *.abc

So benennen Sie die Dateien rekursiv um:

find /path/to/root/folder -type f -name '*.abc' -print0 | xargs -0 rename 's/.abc$/.edefg/'
Chakra
quelle
8
Oder rename 's/.abc$/.edefg/' /path/to/root/folder/**/*.abcin einer modernen Version von Bash.
Adam Byrtek
Vielen Dank, Adam, dass du mir einen Tipp zur rekursiven Verwendung von * .abc in Ordnern gegeben hast!
Rafał Cieślak
Toller Tipp, danke! Wo finde ich weitere Informationen zur Syntax von Regex? Wie ist das sam Anfang und welche anderen Optionen kann ich dort verwenden. Vielen Dank !
Anto
@AdamByrtek Ich habe gerade mit Ihrem Vorschlag auf Utopic gescheitert. ("keine solchen Dateien" oder solche.)
Jonathan Y.
14

Ein Problem bei rekursiven Umbenennungen besteht darin, dass unabhängig von der Methode, mit der Sie die Dateien suchen, der gesamte Pfad renameund nicht nur der Dateiname übergeben wird. Das macht es schwierig, komplexe Umbenennungen in verschachtelten Ordnern vorzunehmen.

Ich benutze finddie -execdirAktion, um dieses Problem zu lösen. Wenn Sie -execdiranstelle von verwenden -exec, wird der angegebene Befehl aus dem Unterverzeichnis ausgeführt, das die übereinstimmende Datei enthält. Anstatt den gesamten Pfad dahin zu führen rename, geht er nur vorbei ./filename. Das macht es viel einfacher, den regulären Ausdruck zu schreiben.

find /the/path -type f \
               -name '*.abc' \
               -execdir rename 's/\.\/(.+)\.abc$/version1_$1.abc/' '{}' \;

Im Detail:

  • -type f bedeutet nur nach Dateien suchen, nicht nach Verzeichnissen
  • -name '*.abc' bedeutet, dass nur Dateinamen gefunden werden, die auf .abc enden
  • '{}'ist der Platzhalter, der die Stelle markiert, an der -execdirder gefundene Pfad eingefügt wird. Die einfachen Anführungszeichen sind erforderlich, damit Dateinamen mit Leerzeichen und Shell-Zeichen behandelt werden können.
  • Die umgekehrten Schrägstriche nach -typeund -namesind das Bash-Zeilenfortsetzungszeichen. Ich verwende sie, um dieses Beispiel besser lesbar zu machen, aber sie werden nicht benötigt, wenn Sie Ihren Befehl in eine einzige Zeile schreiben.
  • Der Backslash am Ende der -execdirZeile ist jedoch erforderlich. Es ist dazu da, das Semikolon zu verlassen, das den von ausgeführten Befehl beendet -execdir. Spaß!

Erklärung des regulären Ausdrucks:

  • s/ Beginn der Regex
  • \.\/stimmen Sie mit der führenden ./ überein, die -execdir eingibt. Verwenden Sie \, um die. und / oder Metazeichen (Hinweis: Dieser Teil variiert je nach Ihrer Version von find. Siehe Kommentar von Benutzer @apollo)
  • (.+)stimmen mit dem Dateinamen überein. Die Klammern erfassen die Übereinstimmung für die spätere Verwendung
  • \.abc Entkomme dem Punkt und passe das ABC an
  • $ Verankern Sie die Übereinstimmung am Ende der Zeichenfolge

  • / markiert das Ende des "Match" -Teils des regulären Ausdrucks und den Beginn des "Ersetzen" -Teils

  • version1_ Fügen Sie diesen Text jedem Dateinamen hinzu

  • $1verweist auf den vorhandenen Dateinamen, da dieser in Klammern angegeben ist. Wenn Sie im Teil "Übereinstimmung" mehrere Sätze von Klammern verwenden, können Sie hier mit $ 2, $ 3 usw. darauf verweisen.
  • .abcDer neue Dateiname endet mit .abc. Das Punkt-Metazeichen muss hier im Abschnitt "Ersetzen" nicht entfernt werden
  • / Ende der Regex

Vor

tree --charset=ascii

|-- a_file.abc
|-- Another.abc
|-- Not_this.def
`-- dir1
    `-- nested_file.abc

Nach

tree --charset=ascii

|-- version1_a_file.abc
|-- version1_Another.abc
|-- Not_this.def
`-- dir1
    `-- version1_nested_file.abc

Hinweis: renameDie Option -n ist nützlich. Es führt einen Trockenlauf durch und zeigt Ihnen, welche Namen es ändern wird, nimmt jedoch keine Änderungen vor.

Christian Long
quelle
Vielen Dank für die Erklärung der Details, aber seltsamerweise, wenn ich das versuche, ist das --execdirnicht in der Führung vorbeigekommen , ./so dass der \.\/Teil in der Regex weggelassen werden kann. Mai das ist, weil ich auf OSX nicht Ubuntu
Apollo
5

Ein weiterer portabler Weg:

find /the/path -depth -type f -name "*.abc" -exec sh -c 'mv -- "$1" "$(dirname "$1")/$(basename "$1" .abc).edefg"' _ '{}' \;
aNeutrino
quelle
4
Willkommen bei Ask Ubuntu! Obwohl dies eine wertvolle Antwort ist, empfehle ich, sie zu erweitern (durch Bearbeiten), um zu erklären, wie und warum dieser Befehl funktioniert.
Eliah Kagan
0

Dies ist, was ich getan und ziemlich genau so gearbeitet habe, wie ich es wollte. Ich habe den mvBefehl verwendet. Ich hatte mehrere .3gpDateien und wollte sie alle in umbenennen.mp4

Hier ist ein kurzer Oneliner dafür:

for i in *.3gp; do mv -- "$i" "ren-$i.mp4"; done

Der durchsucht einfach das aktuelle Verzeichnis, nimmt alle .3gp-Dateien auf und benennt sie dann (mit dem MV) in um ren-name_of_file.mp4

KhoPhi
quelle
Das wird umbenannt file.3gpzu file.3gp.mp4, was vielleicht nicht , was die meisten Leute wollen.
muru
@muru aber das prinzip gibt es noch. Wählen Sie einfach eine Nebenstelle aus und geben Sie die Zielnebenstelle an, um sie umzubenennen. Die .3gpoder .mp4hier waren nur zu Illustrationszwecken.
KhoPhi
Die Syntax ist vorzuziehen, einzeilig und leicht zu verstehen. Ich würde jedoch verwenden basename "$i" .mp4, um die vorherige Erweiterung anstelle von "ren- $ i.mp4" zu entfernen.
TaoPR
Wahr. Guter Punkt.
KhoPhi
0

Ich habe einen einfachen Weg gefunden, dies zu erreichen. Verwenden Sie zum Ändern der Dateierweiterungen vieler Dateien von jpg in pdf Folgendes:

for file in /path/to; do mv $file $(basename -s jpg $file)pdf ; done
Friedlich
quelle
0

Ich würde den Befehl mmv aus dem gleichnamigen Paket verwenden :

mmv ';*.abc' '#1#2.edefg'

Die ;entspricht null oder mehr */und entspricht #1in der Ersetzung. Das *entspricht #2. Die nicht-rekursive Version wäre

mmv '*.abc' '#1.edefg'
MvG
quelle
0

Benennen Sie Dateien und Verzeichnisse mit find -execdir | rename

Wenn Sie Dateien und Verzeichnisse nicht einfach mit einem Suffix umbenennen, ist dies ein gutes Muster:

PATH="$(echo "$PATH" | sed -E 's/(^|:)[^\/][^:]*//g')" \
  find . -depth -execdir rename 's/findme/replaceme/' '{}' \;

Anders -execdirals cdbei der Option " awesome " wird vor dem Ausführen des renameBefehls ein Eintrag in das Verzeichnis erstellt -exec.

-depth Stellen Sie sicher, dass die Umbenennung zuerst bei Kindern und dann bei Eltern erfolgt, um potenzielle Probleme mit fehlenden Elternverzeichnissen zu vermeiden.

-execdir ist erforderlich, weil das Umbenennen bei Eingabepfaden ohne Basisnamen nicht gut funktioniert, z. B. schlägt Folgendes fehl:

rename 's/findme/replaceme/g' acc/acc

Das PATHHacken ist erforderlich, da -execdires einen sehr ärgerlichen Nachteil hat: Es findist äußerst kritisch und weigert sich, irgendetwas damit zu tun, -execdirwenn Sie relative Pfade in Ihrer PATHUmgebungsvariablen haben, z ./node_modules/.bin.

find: Der relative Pfad './node_modules/.bin' ist in der Umgebungsvariablen PATH enthalten, die in Kombination mit der Aktion -execdir von find unsicher ist. Bitte entfernen Sie diesen Eintrag aus $ PATH

Siehe auch: Warum ist die Verwendung der Aktion '-execdir' für Verzeichnisse im PATH unsicher?

-execdirist eine GNU find Erweiterung zu POSIX . renameist Perl-basiert und stammt aus dem renamePaket. Getestet in Ubuntu 18.10.

Benennen Sie die Lookahead-Problemumgehung um

Wenn Ihre Eingabepfade nicht von stammen findoder wenn Sie genug von der relativen Pfadbelästigung haben, können wir einige Perl-Lookaheads verwenden, um Verzeichnisse sicher umzubenennen, wie in:

git ls-files | sort -r | xargs rename 's/findme(?!.*\/)\/?$/replaceme/g' '{}'

Ich habe nicht eine bequeme analog gefunden -execdirmit xargs: https://superuser.com/questions/893890/xargs-change-working-directory-to-file-path-before-executing/915686

Das sort -rist erforderlich, um sicherzustellen, dass Dateien nach ihren jeweiligen Verzeichnissen kommen, da längere Pfade nach kürzeren mit demselben Präfix kommen.

Ciro Santilli ist ein Schauspieler
quelle