it-swarm.com.de

Begrenzen Sie die Anzahl der Übereinstimmungen mit dem Befehl find

Wie mache ich das, wenn der Befehl find nach dem Finden einer bestimmten Anzahl von Übereinstimmungen gestoppt werden soll?

Hintergrund ist, dass ich zu viele Dateien in einem Ordner habe. Ich muss sie zufällig in separate Ordner legen, wie:

find -max-matches 1000 -exec mv {} /path/to/collection1 \+; 
find -max-matches 1000 -exec mv {} /path/to/collection2 \+; 

ist dies nur mit find möglich? Wenn nicht, was wäre der einfachste Weg, dies zu tun?

9
David Dai

Da Sie find nur zum Durchsuchen des Verzeichnisbaums verwenden, würde ich empfehlen, stattdessen die Shell direkt zu verwenden. Siehe Variationen für zsh und bash unten.


Verwenden der zsh Shell

mv ./**/*(-.D[1,1000]) /path/to/collection1    # move first 1000 files
mv ./**/*(-.D[1,1000]) /path/to/collection2    # move next 1000 files

Das Globbing-Muster ./**/*(-.D[1,1000]) würde mit allen regulären Dateien (oder symbolischen Links zu solchen Dateien) im oder unter dem aktuellen Verzeichnis übereinstimmen und dann zuerst die 1000 zurückgeben. Der -. Beschränkt die Übereinstimmung auf reguläre Dateien oder symbolische Links zu diesen, während D sich wie dotglob in bash verhält (entspricht versteckten Namen).

Dies setzt voraus, dass der generierte Befehl durch Erweitern des Globbing-Musters beim Aufrufen von mv nicht zu groß wird.

Das Obige ist ziemlich ineffizient, da es den Globus für jede Sammlung erweitern würde. Möglicherweise möchten Sie daher die Pfadnamen in einem Array speichern und dann Slices davon verschieben:

pathnames=( ./**/*(-.D) )

mv $pathnames[1,1000]    /path/to/collection1
mv $pathnames[1001,2000] /path/to/collection2

So ordnen Sie das Array pathnames beim Erstellen zufällig an (Sie haben erwähnt, dass Sie zufällige Dateien verschieben möchten):

pathnames=( ./**/*(-.Doe['REPLY=$RANDOM']) )

Sie könnten eine ähnliche Aktion in bash ausführen (außer Sie können das Ergebnis einer Glob-Übereinstimmung in bash nicht einfach mischen, abgesehen davon, dass Sie die Ergebnisse möglicherweise über shuf zuführen Ich werde das bisschen überspringen):

shopt -s globstar dotglob nullglob

pathnames=()
for pathname in ./**/*; do
    [[ -f $pathname ]] && pathnames+=( "$pathname" )
done

mv "${pathnames[@]:0:1000}"    /path/to/collection1
mv "${pathnames[@]:1000:1000}" /path/to/collection2
mv "${pathnames[@]:2000:1000}" /path/to/collection3
17
Kusalananda

Sie können neue Tests für find mit -exec Implementieren:

seq 1 1000 |
find . -exec read \; -exec mv {} /path/to/collection1 +

verschiebt die ersten 1000 gefundenen Dateien nach /path/to/collection1.

Dies funktioniert wie folgt:

  • seq 1 1000 Gibt 1000 Zeilen aus, die in find geleitet werden.
  • -exec read Liest eine Zeile und schlägt fehl, wenn die Pipe geschlossen ist (wenn die Ausgabe von seq verbraucht wurde).
  • wenn das vorherige -exec erfolgreich ist, führt -exec mv ... die Verschiebung durch.

-exec ... + Funktioniert wie erwartet: read wird einmal pro Iteration ausgeführt, aber find sammelt übereinstimmende Dateien und ruft mv so oft wie möglich auf.

Dies beruht auf der Tatsache, dass find 's -exec Basierend auf dem Exit-Status des ausgeführten Befehls erfolgreich ist oder fehlschlägt: wann read ist erfolgreich, find verarbeitet die oben angegebenen Aktionen weiter (da der Standardoperator "und" ist), und wenn dies fehlschlägt, wird find gestoppt.

Wenn Ihr find die Aktion -quit Unterstützt, können Sie dies verwenden, um die Effizienz zu verbessern:

seq 1 1000 |
find . \( -exec read \; -o -quit \) -exec mv {} /path/to/collection1 +

Ohne das testet find jede einzelne Datei, obwohl nur 1000 für mv erhalten bleiben.

Ich gehe davon aus, dass read als externer Befehl verfügbar ist und implementiert die POSIX-Spezifikation für read ; Ist dies nicht der Fall, kann stattdessen sh -c read verwendet werden. In beiden Fällen startet find für jede überprüfte Datei einen separaten Prozess.

14
Stephen Kitt

Ich denke nicht, dass es mit nur find gemacht werden kann. Sie können etwas verwenden wie:

find [... your parameters ...] -print0 | head -z -1000 | xargs -0 mv -t /path/to/collection

-print0, -z, und -0 arbeiten zusammen, um sicherzustellen, dass alles auch mit Zeilenvorschüben in Dateinamen funktioniert.

10
xenoid

Ich denke, es ist nicht direkt mit find möglich, aber Sie können eine Pfeife mit Kopf und xargs verwenden wie:

finden ... | Kopf -1000 | xargs -i mv "{}/path/to/collection1"

Dadurch werden die ersten 1000 Dateien in Sammlung1 verschoben.

4
stk