Ich habe einen Fehler gemacht und Dateien zusammen in das gleiche Verzeichnis gepackt. Zum Glück kann ich sie nach Dateinamen sortieren: ''' 2019-02-19 20.18.58.ndpi_2_2688_2240.jpg ''' Wobei die#Bit oder2in diesem konkreten Fall handelt es sich sozusagen um die Standortinformation. Der Bereich ist 0-9 und die Dateinamen sind alle gleich lang, so dass die Zahl immer an der gleichen Stelle des Dateinamens steht (26. Zeichen mit Leerzeichen, flankiert von Unterstrichen). Ich habe diesen tollen Link gefunden: Befehl zum Suchen von Dateien durch die Suche nach nur einem Teil ihrer Namen?
Ich kann die Ausgabe jedoch nicht in den Move-Befehl weiterleiten. Ich habe versucht, die Ausgabe in eine Variable zu schleifen, aber das schien auch nicht zu funktionieren:
for f in find . -name '*_0_*' ; do mv "$f" /destination/directory ; done
Basierend auf diesem Link habe ich das * oder einige Anführungszeichen möglicherweise an der falschen Stelle platziert:mv: kann nicht stat Keine solche Datei oder kein solches Verzeichnis im Shell-Skript.
Allerdings habe ich viele Verzeichnisse und möchte sie an einer anderen Stelle in eine identische Verzeichnisstruktur sortieren:
-Flowers (to be sorted) -Flowers-buds -Flowers-stems
-Roses -Roses -Roses
buds.jpg ===> buds.jpg ===> stems.jpg
stems.jpg
petals.jpg
-Daisies -Daisies -Daisies
buds.jpgs buds.jpg stems.jpg
stems.jpg
petals.jpg
-Tulips -Tulips -Tulip
buds.jpgs buds.jpg stems.jpg
stems.jpg
petals.jpg
... und mehr basierend auf dieser Zahl (#). Ist das in der Bash praktisch umsetzbar? Ich verwende MacOS im Terminal mit installierten Coreutils, daher sollten sich die Tools wie GNU Linux und nicht wie Darwin (BSD) verhalten.
Antwort1
Sie können dies mit find
und tun mv
, aber das ist hier nicht der einfachste Ansatz. Sie verwenden macOS, alsozshist vorinstalliert. Zsh verfügt über ein praktisches Tool zum Massenumbenennen von Dateien namenszmv
. Führen Sie es zsh
in einem Terminal aus und führen Sie dann in zsh etwas wie Folgendes aus:
autoload zmv
zmv -n '[^_]#_([0-9])_*' '/elsewhere/${1}/${f}'
Erläuterungen:
-n
weist anzmv
, anzuzeigen, was es tun würde, tut aber nichts. Wenn Sie mit der Anzeige zufrieden sind, führen Sie den Befehl erneut ohne aus-n
.- Das erste Argument, das keine Option ist, ist einPlatzhaltermuster.
zmv
wird auf die übereinstimmenden Dateien reagieren (und nicht übereinstimmende Dateien ignorieren). [^_]#
bedeutet null oder mehr Zeichen außer_
. Zsh gibt Ihnen Zugriff auf dieselben Platzhalter wie Bash und mehr.#
ist eine nur in Zsh verfügbare Funktion (in Bash mit einer anderen Syntax verfügbar), die „jede beliebige Zahl des vorherigen Zeichens“ bedeutet. Das Muster[^_]#_[0-9]_*
entspricht jedem Dateinamen, der eine einzelne Ziffer zwischen zwei Unterstrichen und keinen weiteren Unterstrich vor dieser Ziffer enthält.- Die Klammern darum
[0-9]
machen die Ziffer wie$1
im Ersetzungstext verfügbar. - Der Ersetzungstext kann
${1}
,${2}
, usw. verwenden, um auf eingeklammerte Fragmente im Quellmuster zu verweisen und${f}
auf den vollständigen Originalnamen zu verweisen.
Beispielsweise verschiebt der obige Codeausschnitt 2019-02-19 20.18.58.ndpi_2_2688_2240.jpg
nach /elsewhere/2/2019-02-19 20.18.58.ndpi_2_2688_2240.jpg
. Ihre Frage beschreibt nicht sehr genau, was verschoben werden muss und wohin es verschoben werden muss, aber Sie sollten das gewünschte Ergebnis erhalten, indem Sie den obigen Befehl anpassen. Wenn Sie es nicht herausfinden können, bearbeiten Sie Ihre Frage, um spezifische Beispiele dafür hinzuzufügen, was übereinstimmen muss und was nicht.
Wenn Sie Dateien in Unterverzeichnissen haben, können Sie */
am Anfang des Musters verwenden. Wenn Sie das Verzeichnis abgleichen müssen, um darauf zu verweisen , müssen Sie Klammern um das setzen : Sie können keine Klammern um einen Schrägstrich setzen. Wenn Sie das Verzeichnis rekursiv durchlaufen und Dateien an beliebiger Stelle abgleichen möchten, verwenden Sie for${NUM}
*
**/
rekursives Globbing. Als Ausnahme müssen Sie verwenden, (**/)
um auf den Verzeichnispfad im Ersetzungstext als zuzugreifen . In Fällen wie diesem ist es oft einfacher, keine Klammern zu verwenden und stattdessen${NUM}
Modifikatorenon ${f}
, um Teile des ursprünglichen Pfads als Ganzes zu extrahieren. Führen Sie beispielsweise die gleiche Umbenennung wie oben durch, verschieben Sie jedoch Dateien aus dem aktuellen Verzeichnis in eine parallele Struktur unter /elsewhere
, jedoch mit einer zusätzlichen Ebene 0
, 1
, usw. direkt vor dem Dateinamen:
autoload zmv
zmv -n '**/[^_]#_([0-9])_*' '/elsewhere/${f:h}/${1}/${f:t}'
Wenn es schwierig ist, die Dateien anhand ihres Namens abzugleichen, wäre ein anderer Ansatz, sie anhand ihrer Änderungszeit abzugleichen¹. Wenn Sie gerade eine Reihe von Dateien in ein Verzeichnis kopiert haben, das sich seit einiger Zeit nicht geändert hat, sind die neuen Dateien diejenigen, die eine aktuelle Änderungszeit haben. Sie können Dateien anhand der Änderungszeit in zsh mit demGlob-Qualifiziererc
Um beispielsweise die Dateien aufzulisten, die in der letzten Stunde zuletzt geändert wurden:
ls -lctr *(ch-1)
Anstatt eine Frist zu finden, können Sie sich an denNzuletzt geänderte Dateien mit den Glob-Qualifizierern oc
(um nach aufsteigender ctime zu sortieren) und (um nur die ersten[1,N]
NÜbereinstimmungen). Wenn Sie beispielsweise wissen, dass Sie gerade 42 Dateien in dieses Verzeichnis verschoben haben und seitdem nichts daran geändert haben:
ls -lctr *(oc[1,42])
Wenn Sie Glob-Qualifizierer mit verwenden möchten zmv
, müssen Sie die -Q
Option übergeben. So verschieben Sie beispielsweise Dateien wie oben basierend auf ihrem Namen in ein Verzeichnis, ignorieren jedoch alle Dateien, die sich in der letzten Stunde nicht geändert haben:
zmv -n -Q '[^_]#_([0-9])_*(ch-1)' '/elsewhere/${1}/${f}'
zmv
verfügt über einige Sicherheitsvorkehrungen, um sicherzustellen, dass keine Dateien überschrieben werden und dass es keine Kollisionen gibt (zwei Quelldateien mit demselben Zielnamen). Wenn Sie eine große Anzahl von Dateien haben, können diese Sicherheitsvorkehrungen einige Zeit in Anspruch nehmen. Wenn zmv
es zu langsam ist und die Struktur Ihrer Umbenennung garantiert, dass es keine Kollisionen gibt, können Sie die spezifische Umbenennung, die Sie durchführen, in einer Schleife fest codieren. Zsh ist tendenziell besser als Bash, auch wenn Sie es nicht verwenden, zmv
dank seiner schönerenGlotzenUndParametererweiterungMechanismen. Für die Leistung können Siedynamisch ladenein Modul, das enthältBuiltins zur Dateimanipulation.
Um beispielsweise normale Dateien aus dem aktuellen Verzeichnis in eine völlig neue Hierarchie zu verschieben, wenn ihr Name Folgendes enthält _0_
:
zmodload -m -F zsh/files 'b:zf_*'
for x in **/*_0_*(.); do
zf_mkdir -p /destination/directory/$x:h
zf_mv ./$x /destination/directory/$x
done
( :h
ist ein weiteres zsh-Feature: einVerlaufsmodifikator.)
Um die erste Zahl zwischen zwei Unterstrichen im Dateinamen zu nehmen und Dateien in einem nach dieser Zahl benannten Verzeichnis abzulegen, müssen Sie die Zahl extrahieren. zmv
Dies geschieht automatisch für Gruppen in Klammern, hier müssen wir es von Hand tun.
zmodload -m -F zsh/files 'b:zf_*'
for source in **/*_<->_*(.); do
suffix=${${source:t}#*_<->_}
n=${${source%$suffix}##*_}
destination=${source:h}/$n/${source:t}
zf_mkdir -p /destination/directory/$destination:h
zf_mv ./$source /destination/directory/$destination
done
Wenn Sie mehr über andere Methoden zur Massenumbenennung von Dateien erfahren möchten, können Sierename
Auf dieser Site markierte Fragen.
¹ Die Inode-Änderungszeit einer Datei, kurz „Änderungszeit“ oder „ctime“ genannt, ist der Zeitpunkt, zu dem die Datei zuletzt verschoben wurde oder ihre Attribute wie Berechtigungen zuletzt geändert wurden. Sie unterscheidet sich von der Änderungszeit (mtime).
Antwort2
Die Shell teilt die Eingabe bei Leerzeichen auf. Sie können Bash-Globbing mit der rekursiven Methode verwenden, **
um die Dateinamen richtig aufzuteilen:
shopt -s globstar
for f in **/*_0_*; do mv "$f" /dest/; done
Wenn sie alle zum selben Ort gehen, ist die Schleife nicht erforderlich:
shopt -s globstar
mv **/*_0_* /dest/
… oder verwenden Sie find -exec
, das Dateinamen direkt von find an einen exec()
Aufruf übergibt, ohne Beteiligung der Shell:
find . -name '*_0_*' -exec mv {} /dest/ \;
Oder verwenden Sie read
plus, find -print0
um bei Null zu teilen. Das ist für dieses Problem übertrieben, nützlich beim Globbing und find -exec
ist der Aufgabe nicht gewachsen:
while IFS= read -r -d ''; do mv "$REPLY" /dest/; done < <( find . -name '*_0_*' -print0 )
Um das Ziel der obersten Ebene basierend auf dem Dateinamen zu ändern, wie in Ihrem Beispiel, schneiden Sie Teile des Dateinamens mit ${var#remove_from_start}
und ab ${var%remove_from_end}
. Platzhalter in den Entfernungsmustern entfernen so wenig wie möglich. Um so viel wie möglich zu entfernen, verdoppeln Sie das Operationszeichen, wie in ${…##…}
oder ${…%%…}
:
shopt -s globstar
for f in **/*_0_*; do # Flowers/Roses/stems.jpg
file=${f##*/} # stems.jpg
base=${file%.*} # stems
path=${f%/*} # Flowers/Roses
toppath=${path%%/*} # Flowers
subpath=${path#*/} # Roses
dest=$toppath-$base/$subpath/ # Flowers-stems/Roses/
[[ -d $dest ]] || mkdir -p "$dest"
mv "$f" "$dest"
done