So durchsuchen Sie einen Ordner und alle seine Unterordner nach Dateien eines bestimmten Typs

70

Ich versuche, nach allen Dateien eines bestimmten Typs in einem bestimmten Ordner zu suchen und sie in einen neuen Ordner zu kopieren.

Ich muss einen Stammordner angeben und diesen Ordner und alle seine Unterordner nach Dateien durchsuchen, die dem angegebenen Typ entsprechen.

Wie durchsuche ich die Unterordner des Stammordners und ihre Unterordner? Es scheint, als würde eine rekursive Methode funktionieren, aber ich kann eine nicht richtig implementieren.

agentbanks217
quelle

Antworten:

64

Sie möchten das Suchmodul . Find.findNimmt eine Zeichenfolge, die einen Pfad enthält, und übergibt den übergeordneten Pfad zusammen mit dem Pfad jeder Datei und jedes Unterverzeichnisses an einen zugehörigen Block. Ein Beispielcode:

require 'find'

pdf_file_paths = []
Find.find('path/to/search') do |path|
  pdf_file_paths << path if path =~ /.*\.pdf$/
end

Dadurch wird rekursiv ein Pfad durchsucht und alle Dateinamen, die mit .pdf enden, in einem Array gespeichert.

Jergason
quelle
116

Versuche dies:

Dir.glob("#{folder}/**/*.pdf")

das ist das gleiche wie

Dir["#{folder}/**/*.pdf"]

Dabei ist die Ordnervariable der Pfad zum Stammordner, den Sie durchsuchen möchten.

Rogerdpack
quelle
3
Ich denke, das OP wollte rekursiv sein, nicht wahr?
Rogerdpack
2
@rogerdpack Soweit ich weiß, ist diese Methode rekursiv. Die Antwort sollte eigentlich sein Dir.glob("#{folder}/**/*.pdf"), wobei die folderVariable der Pfad zum Stammordner ist, den Sie durchsuchen möchten.
Automatico
1
Standardmäßig auch ohne
Berücksichtigung der
2
@Konstantin Dies oder Dir#[], was ich normalerweise benutze. Es gibt jedoch einen Haken: Dir.globLädt alle Pfade in den Speicher. Dies ist normalerweise in Ordnung, aber wenn Sie eine große Anzahl von Pfaden haben, bevorzugen Sie möglicherweise stattdessen das Suchmodul, da es Pfade an den Block liefert, sobald er sie findet.
Wayne Conrad
2
Ich stimme @WayneConrad darin zu. Sie können Ihr Programm versehentlich anhalten, da Ruby genügend Speicher zum Speichern eines großen Arrays reserviert. Dies ist dem Schlürfen einer Datei sehr ähnlich . Es ist effizienter und wahrscheinlich schneller, Finddie Hierarchie verarbeiten zu lassen , als sie auf das Betriebssystem zu werfen und möglicherweise ein unerwartetes Array zu erhalten. Das Debuggen dieser Situation ist schwierig.
der Blechmann
27

Wenn Geschwindigkeit ein Problem ist, ziehen Dir.globSie es vor Find.find.

Warming up --------------------------------------
           Find.find   124.000  i/100ms
            Dir.glob   515.000  i/100ms
Calculating -------------------------------------
           Find.find      1.242k (± 4.7%) i/s -      6.200k in   5.001398s
            Dir.glob      5.249k (± 4.5%) i/s -     26.265k in   5.014632s

Comparison:
            Dir.glob:     5248.5 i/s
           Find.find:     1242.4 i/s - 4.22x slower

 

require 'find'
require 'benchmark/ips'

dir = '.'

Benchmark.ips do |x|
  x.report 'Find.find' do
    Find.find(dir).select { |f| f =~ /\*\.pdf/ }
  end

  x.report 'Dir.glob' do
    Dir.glob("#{dir}/**/*\.pdf")
  end

  x.compare!
end

Verwenden von ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-darwin15]

Dennis
quelle
2
Danke für den Beitrag. Für Anfänger wie mich ist es sehr hilfreich herauszufinden, welche Methode ich unter Dir.globvs verwenden soll Find.find.
Es ist der
5
Die Suche sollte in diesem Fall langsamer sein, da Sie mit einer Regex suchen. Dir.glob hingegen ist nicht so leistungsfähig wie ein Regex, daher würde ich erwarten, dass es schneller ist.
Hirowatari
Ich nehme an, Sie könnten #end_with?sie etwas genauer vergleichen ...
Rogerdpack
1
@hirowatari Regex oder nicht macht keinen Unterschied - Sie können den gesamten Blockinhalt durch ersetzen falseund es wird immer noch deutlich langsamer (probieren Sie es aus). Dies liegt daran, dass das Aufrufen eines Blocks auch einige Zeit in Anspruch nimmt und für jedes gefundene Element erfolgt, während globFilter intern und erst dann zurückgegeben werden, wenn die Ergebnisse erfasst wurden . Daher kann der verwendete Filter findso kompliziert sein, wie Sie möchten. Er kann aus 100 Codezeilen mit Suchvorgängen und mehreren regulären Ausdrücken bestehen, während globnur ein einfaches Muster pro Aufruf verstanden wird. Wenn Sie Ihre Suche auf diese Weise ausdrücken können, bevorzugen Sie glob.
Mecki
Aber wenn Sie tatsächlich etwas mit diesen Dateien tun müssen, müssen Sie für jede von ihnen etwas aufrufen. Je nach Anwendungsfall kann der Vergleich also fair sein oder auch nicht. Auch bei großen Verzeichnisbäumen möchte man möglicherweise nicht das gesamte Array im Speicher speichern. So wäre manchmal einer besser, ein anderes Mal der andere.
Akostadinov
12

Als kleine Verbesserung der obigen Antwort von Jergason und Matt können Sie Folgendes zu einer einzigen Zeile zusammenfassen:

pdf_file_paths = Find.find('path/to/search').select { |p| /.*\.pdf$/ =~ p }

Dies verwendet die Find-Methode wie oben, nutzt jedoch die Tatsache, dass das Ergebnis eine Aufzählung ist (und als solche können wir select verwenden), um ein Array mit dem Satz von Übereinstimmungen zurückzugewinnen

chrisdurheim
quelle
0

Eine andere schnelle Möglichkeit besteht darin, die Aufgabe an den Shell-Befehl "find" zu delegieren und die Ausgabe aufzuteilen:

pdf_file_paths = `find #{dir} -name "*.pdf"`.split("\n")

Funktioniert nicht unter Windows.

Felipe Zavan
quelle