Löschen Sie alle Dateien außer in einem bestimmten Unterverzeichnis mit find

11

Ich möchte rekursiv alle Dateien löschen a, auf die seit einiger Zeit nicht mehr im Ordner zugegriffen wird , mit Ausnahme aller Dateien im Unterordner b.

find a \( -name b -prune \) -o -type f -delete

Ich erhalte jedoch eine Fehlermeldung:

find: Die Aktion -delete aktiviert automatisch -depth, aber -prune führt nichts aus, wenn -depth aktiviert ist. Wenn Sie trotzdem weitermachen möchten, verwenden Sie einfach explizit die Option -depth.

Durch -depthdas Hinzufügen werden alle Dateien beingeschlossen, was nicht passieren darf .

Kennt jemand einen sicheren Weg, um diese Arbeit zu machen?

Forthrin
quelle
@ MichaelKjörling: Ich habe mir extglob angesehen, aber wie schließt man alles aaußer ein a/b?
Forthrin
Wird nicht cd a && ls -d !(b/*)funktionieren? (Um es zu tun, nur rm -ranstatt ls -d.)
ein CVn
Ihr Vorschlag löscht Unterordner. Ich möchte die Ordner intakt halten. Ich möchte alle Dateien im Baum unter finden und löschen a(außer Dateien unter a/b).
Forthrin
Also einfach überspringen -rzu rm. Es scheint, dass das, worüber Sie fragen, ziemlich einfach mit dem erweiterten Globbing von bash beantwortet werden kann, und was Sie dann mit dem Ergebnis des Globbings tun, liegt bei Ihnen.
Ein Lebenslauf vom
@ MichaelKjörling Nur weil die beiden Probleme vage ähnliche Lösungen haben, werden die Fragen nicht doppelt. Die meisten Lösungen für jedes der beiden Probleme lösen das andere Problem nicht.
Gilles 'SO - hör auf böse zu sein'

Antworten:

13

TL; DR: Der beste Weg ist, -exec rmstatt zu verwenden -delete.

find a \( -name b -prune \) -o -type f -exec rm {} +

Erläuterung:

Warum beschwert sich find, wenn Sie versuchen, -deletemit zu verwenden -prune?

Kurze Antwort: weil -deleteimpliziert -depthund unwirksam -depthmacht -prune.

Bevor wir zur langen Antwort kommen, beobachten Sie zunächst das Verhalten des Findens mit und ohne -depth:

$ find foo/
foo/
foo/f1
foo/bar
foo/bar/b2
foo/bar/b1
foo/f2

Es gibt keine Garantie für die Bestellung in einem einzigen Verzeichnis. Es besteht jedoch die Garantie, dass ein Verzeichnis vor seinem Inhalt verarbeitet wird. Beachten Sie foo/vor jedem foo/*und foo/barvor jedem foo/bar/*.

Dies kann mit umgekehrt werden -depth.

$ find foo/ -depth
foo/f2
foo/bar/b2
foo/bar/b1
foo/bar
foo/f1
foo/

Beachten Sie, dass jetzt alle foo/*vorher erscheinen foo/. Gleiches gilt für foo/bar.

Längere Antwort:

  • -pruneverhindert, dass find in ein Verzeichnis absteigt. Mit anderen Worten, -pruneüberspringt den Inhalt des Verzeichnisses. In Ihrem Fall wird -name b -pruneverhindert, dass die Suche in ein Verzeichnis mit dem Namen absteigt b.
  • -depthmacht find, um den Inhalt eines Verzeichnisses vor dem Verzeichnis selbst zu verarbeiten. Das heißt, bis find den Verzeichniseintrag verarbeiten kann, ist bsein Inhalt bereits verarbeitet. Somit -pruneist -depthin der Tat unwirksam .
  • -deleteimpliziert, -depthdass zuerst die Dateien und dann das leere Verzeichnis gelöscht werden können. -deleteweigert sich, nicht leere Verzeichnisse zu löschen. Ich denke, es wäre möglich, eine Option hinzuzufügen, um -deletedas Löschen nicht leerer Verzeichnisse zu erzwingen und / oder zu verhindern -delete, dass dies impliziert wird -depth. Aber das ist eine andere Geschichte.

Es gibt noch einen anderen Weg, um das zu erreichen, was Sie wollen:

find a -not -path "*/b*" -type f -delete

Dies kann leichter zu merken sein oder auch nicht.

Dieser Befehl steigt immer noch in das Verzeichnis ab bund verarbeitet jede einzelne Datei darin nur -not, um sie abzulehnen. Dies kann ein Leistungsproblem sein, wenn das Verzeichnis sehr bgroß ist.

-pathfunktioniert anders als -name. -nameStimmt nur mit dem Namen (der Datei oder des Verzeichnisses) überein, während er -pathmit dem gesamten Pfad übereinstimmt. Beobachten Sie zum Beispiel den Weg /home/lesmana/foo/bar. -name -barwird übereinstimmen, weil der Name ist bar. -path "*/foo*"wird übereinstimmen, da sich die Zeichenfolge /fooim Pfad befindet. -pathhat einige Feinheiten, die Sie verstehen sollten, bevor Sie es verwenden. Lesen Sie die Manpage von findfür weitere Details.

Beachten Sie, dass dies nicht 100% narrensicher ist. Es besteht die Möglichkeit von "False Positives". Die Art und Weise, wie der Befehl oben geschrieben wird, überspringt alle Dateien, deren übergeordnetes Verzeichnis mit dem Namen beginnt b(positiv). Es wird jedoch auch jede Datei übersprungen, mit deren Namen bunabhängig von der Position im Baum begonnen wird (falsch positiv). Dies kann behoben werden, indem ein besserer Ausdruck als geschrieben wird "*/b*". Das bleibt als Übung für den Leser.

Ich gehe davon aus, dass Sie aund bals Platzhalter verwendet haben und die richtigen Namen eher wie allosaurusund sind brachiosaurus. Wenn Sie brachiosaurusanstelle von setzen, bwird die Anzahl der falsch positiven Ergebnisse drastisch reduziert.

Zumindest die Fehlalarme werden nicht gelöscht, so dass es nicht so tragisch ist. Darüber hinaus können Sie nach Fehlalarmen suchen, indem Sie zuerst den Befehl ohne ausführen -delete(aber denken Sie daran, das implizite zu platzieren -depth) und die Ausgabe untersuchen.

find a -not -path "*/b*" -type f -depth
Lesmana
quelle
-not -pathwar genau das Richtige! Danke für eine großzügige Erklärung!
Forthrin
1
Eine Ausarbeitung, warum -not -pathfunktioniert, während -prunees nicht funktioniert, wäre hilfreich. Warum kann man -not -pathmit koexistieren -depth?
Faheem Mitha
3

Verwenden Sie einfach rmanstelle von -delete:

find a -name b -prune -o -type f -exec rm -f {} +
Stéphane Chazelas
quelle
1
Können Sie näher erläutern, warum dies rmfunktioniert und warum deletenicht?
Faheem Mitha
1
Oh, ich denke vielleicht, weil "-delete sich weigert, nicht leere Verzeichnisse zu löschen.", Um @lesmana zu zitieren. Weigert sich daher, nicht leere Verzeichnisse zu löschen. Hat rmaber kein Problem. Trotzdem wäre eine Ausarbeitung eine gute Sache.
Faheem Mitha
@FaheemMitha, die Antwort darauf ist in der Frage. -deleteimpliziert -depth, was offensichtlich nicht funktionieren kann -prune. -pathfunktioniert, hört aber nicht auf find, in Verzeichnissen abzusteigen, die es nicht erkunden muss.
Stéphane Chazelas
0

Die obigen Antworten und Erklärungen waren sehr hilfreich.

Ich verwende die Problemumgehungen von "-exec rm {} +" oder "-not -path ... -delete", aber diese können viel langsamer sein als "find ... -delete". Ich habe "find ..." gesehen. -delete "wird 5x schneller ausgeführt als" -exec rm {} + "in tiefen Verzeichnissen eines NFS-Dateisystems.

Die '-not path'-Lösung hat den offensichtlichen Aufwand, alle Dateien in den ausgeschlossenen Verzeichnissen und darunter zu betrachten.

Das "find .. -exec rm {} +" ruft rm auf, das Systemaufrufe ausführt:

fstatat(AT_FDCWD, path...); 
unlinkat(AT_FDCWD, path, 0)

Das "find -delete" führt Systemaufrufe aus:

 fd=open(dir,...);
 fchdir(fd); 
 fstatat(AT_FDCWD, filename,...)
 unlinkat(dirfd, filename,...)

Der Befehl "-exec rm {} +" rm führt den vollständigen Pfad zur Inode-Suche zweimal pro Datei aus, aber "find -delete" führt eine Statistik durch und hebt die Verknüpfung des Dateinamens im aktuellen Verzeichnis auf. Das ist ein großer Gewinn, wenn Sie viele Dateien in einem Verzeichnis entfernen.

(Jammern Modus an (sorry))

Es scheint, als würde das Design der Interaktion zwischen -depth, -delete und -prune die effizienteste Methode zum Löschen der allgemeinen Aktion "Dateien löschen, außer denen in -prune-Verzeichnissen" unnötig eliminieren.

Die Kombination von "-type f -delete" sollte ohne -depth ausgeführt werden können, da nicht versucht wird, Verzeichnisse zu löschen. Wenn "find" eine Aktion "-deletefile" hätte, die besagt, dass Verzeichnisse nicht gelöscht werden sollen, müsste -depth nicht impliziert werden.

Die Aufrufe xargs oder find -exec für den Befehl rm könnten beschleunigt werden, wenn rm die Option hätte, Dateinamen zu sortieren, Verzeichnisse zu öffnen und die Verknüpfung aufzuheben (dir_fd, Dateiname), anstatt die Verknüpfung der vollständigen Pfade aufzuheben. Das Unlinkat (dir_fd, Dateiname) wird bereits ausgeführt, wenn mit der Option -r durch Verzeichnisse rekursiert wird.

(Jammern Modus aus)

Donald Mears
quelle