Verschieben Sie Dateien aus Unterverzeichnissen in ein einzelnes Verzeichnis und geben Sie den ursprünglichen Verzeichnisnamen an

1424
Brad Dwyer

Ich habe eine Verzeichnisstruktur wie folgt:

./a/1.png ./a/2.png ./a/3.png ./b/1.png ./b/2.png ./b/3.png ./c/1.png ... 

Ich möchte alle Dateien in den Unterverzeichnissen mitnehmen und sie in ein neues Verzeichnis verschieben, damit ihre Namen so aussehen

../dest/a_1.png ../dest/a_2.png ../dest/a_3.png ../dest/b_1.png ../dest/b_2.png ../dest/b_3.png ../dest/c_1.png ... 

Die beste Möglichkeit, die ich finden konnte, ohne ein Skript schreiben zu müssen, um Datei für Datei zu erstellen, ist finddie --backup=numberedOption mit der Option, die meine Dateien in einem einzigen Verzeichnis zusammenfasst, den Verzeichniskontext jedoch aus dem Dateinamen verliert.

Gibt es einen knappen Weg, dies zu erreichen?

3
Warum nicht ein Skript machen? var firstName vor 6 Jahren 1
Ich habe am Ende ein (super ineffizientes) Skript geschrieben (in nodejs ... das ist das, was ich am besten kenne), aber ich hoffte, etwas Neues zu lernen :) Brad Dwyer vor 6 Jahren 0

4 Antworten auf die Frage

8
Cyrus

Mit dem eigenständigen renameBefehl von Perl :

rename -n 's|/|_|; s|^|dest/|' */*.png 

Ausgabe:

a / 1.png wurde in dest / a_1.png umbenannt a / 2.png umbenannt in dest / a_2.png a / 3.png umbenannt in dest / a_3.png b / 1.png in dest / b_1.png umbenannt b / 2.png in dest / b_2.png umbenannt b / 3.png in dest / b_3.png umbenannt c / 1.png in dest / c_1.png umbenannt 

Wenn alles gut aussieht, entfernen Sie die Option -n.

Er ist sehr schlau und ersetzt einfach `/` durch `_` in diesem speziellen Fall. slhck vor 6 Jahren 1
Schön, ich wusste nichts über Perl `Rename` - sieht nützlich aus! Brad Dwyer vor 6 Jahren 0
Der andere Ansatz von Jochen Lutz mit `mmv` ist ebenfalls möglich:` Umbenennen von -n 's (. *) / (. *) | Dest / $ 1_ $ 2 |' * / *. png` Cyrus vor 6 Jahren 0
6
slhck

Mit Bash 4 und rekursivem Globbing ( shopt -s globstar):

for f in **/*.png; do dn=$(basename "$(dirname "$f")") bn=$(basename "$f") mv -- "$f" "../dest/$_$" done 

Beachten Sie, dass dirname /foo/bar/bazgibt /foo/barund nicht nur bar, weshalb Sie anrufen müssen basenameauf das Ergebnis nur um bar, wenn Sie von einem anderen übergeordneten Ordner arbeiten.

3
AFH

Als alternativer Ansatz, benötigen Sie keine externen Programme wie rename, basenameusw. - es können alle innerhalb behandelt werden bashParameter Expansion: -

find SourceDir ... | while read -r f; do mv "$f" "TargetDir/$"; done 

Die Erweiterung ist etwas schwer zu folgen, also passiert Folgendes: -

  • Die zu verschiebenden Dateien werden mit gefunden find.
  • Jeder Dateiname wird nacheinander gelesen $f.
  • Der read -rParameter und die doppelten Anführungszeichen um die Erweiterungen behandeln seltsame Namen, einschließlich Leerzeichen in den Dateinamen.
  • Der mvBefehl verschiebt Namen wie a/b/czu TargetDir/a_b_c.
  • Die Zielerweiterung ersetzt jedes /by _, sie ist jedoch entmutigend, da sie /Teil der Substitutionssyntax ist.
  • Die allgemeine Form ist $, die die erste Instanz von oldby newin der Erweiterung ersetzt.
  • Die Form, die hier benötigt wird $, ist die, die jede Instanz oldvon newin der Erweiterung ersetzt.
  • Um /Teil des alten Strings zu sein, muss dieser als \/Escape-Code maskiert werden. Daher ist das eher undeutlich $: Der erste Befehl /führt die Substitutionssyntax ein, der zweite /gibt jeden ein, der dritte (Escape-Code) /ist der alte und der letzte String /die neue Zeichenfolge ( _).

Ich sehe diese Form der Erweiterung nicht oft in Skripten, aber es lohnt sich zu wissen, da sie manchmal sehr nützlich sein kann.

Es gibt einige Dateinamen, die dies zerstören (eingebettete neue Zeilenzeichen und führende oder nachgestellte Leerzeichen), wobei letztere auf zwei Arten möglich ist: -

  • Verwenden Sie read -rstatt read -r fund verwenden Sie REPLYstatt f.
  • Verwenden Sie while f="$(line)"statt read -r f.

Letzteres ist sauberer, verwendet jedoch das externe Programm line, das möglicherweise nicht auf allen Systemen verfügbar ist, obwohl es als Funktion codiert werden kann:

line() { read -r; r=$?; echo "$REPLY"; return $r; } 
Zu "es kann alles innerhalb der bash-Parametererweiterung gehandhabt werden", geht das eher davon aus, dass Sie bash verwenden, nein? Während Perl & c Shell-Agnostiker sind, AFAIK. jamesqf vor 6 Jahren 0
@jamesqf - Der Fragesteller hat "bash" als Tag eingefügt, daher die Annahme. AFH vor 6 Jahren 1
2
Jochen Lutz

Als Alternative zum Umbenennen gibt es mmv, das standardmäßige Schalenmuster verwendet.

Aus der Manpage:

Mmv verschiebt (oder kopiert, fügt oder verbindet, wie angegeben) jede Quelldatei, die einem from- Muster entspricht, mit dem durch das to- Muster angegebenen Zielnamen . Diese mehrfache Aktion wird sicher ausgeführt, dh ohne unerwartete Löschung von Dateien aufgrund von Kollisionen von Zielnamen mit vorhandenen Dateinamen oder mit anderen Zielnamen. Bevor mmv etwas unternimmt, versucht er außerdem, Fehler zu entdecken, die sich aus den gesamten angegebenen Aktionen ergeben würden, und gibt dem Benutzer die Wahl, entweder fortzufahren, indem er die störenden Teile vermeidet, oder abbrechen.

Für Ihren Anwendungsfall:

mmv -n '*/*' 'dest/#1_#2' a/1.jpg -> dest/a_1.jpg a/2.jpg -> dest/a_2.jpg a/3.jpg -> dest/a_3.jpg b/1.jpg -> dest/b_1.jpg b/2.jpg -> dest/b_2.jpg b/3.jpg -> dest/b_3.jpg c/1.jpg -> dest/c_1.jpg c/2.jpg -> dest/c_2.jpg c/3.jpg -> dest/c_3.jpg 

Wie beim Umbenennen ist '-n' keine Ausführung. Entfernen Sie es für die tatsächliche Umbenennung.

Neben dem Umbenennen unterstützt mmv auch das Kopieren, Verknüpfen oder Anhängen der Dateien.