Sortieren und mv-Dateien basierend auf dem Dateinamen (mit Leerzeichen), rekursiv

Sortieren und mv-Dateien basierend auf dem Dateinamen (mit Leerzeichen), rekursiv

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 findund 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 zshin 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:

  • -nweist an zmv, 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. zmvwird 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 $1im 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.jpgnach /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-QualifizierercUm 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 -QOption ü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}'

zmvverfü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 zmves 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, zmvdank 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

( :hist 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. zmvDies 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 SierenameAuf 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 readplus, find -print0um bei Null zu teilen. Das ist für dieses Problem übertrieben, nützlich beim Globbing und find -execist 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

verwandte Informationen