Gibt es eine gute "scala-esque" (ich meine ich meine funktionale) Möglichkeit, Dateien in einem Verzeichnis rekursiv aufzulisten? Was ist mit einem bestimmten Muster?
Zum Beispiel rekursiv alle entsprechenden Dateien "a*.foo"
in c:\temp
.
Gibt es eine gute "scala-esque" (ich meine ich meine funktionale) Möglichkeit, Dateien in einem Verzeichnis rekursiv aufzulisten? Was ist mit einem bestimmten Muster?
Zum Beispiel rekursiv alle entsprechenden Dateien "a*.foo"
in c:\temp
.
Antworten:
Scala-Code verwendet normalerweise Java-Klassen für den Umgang mit E / A, einschließlich des Lesens von Verzeichnissen. Sie müssen also Folgendes tun:
import java.io.File
def recursiveListFiles(f: File): Array[File] = {
val these = f.listFiles
these ++ these.filter(_.isDirectory).flatMap(recursiveListFiles)
}
Sie können alle Dateien sammeln und dann mit einem regulären Ausdruck filtern:
myBigFileArray.filter(f => """.*\.html$""".r.findFirstIn(f.getName).isDefined)
Oder Sie können den regulären Ausdruck in die rekursive Suche einbeziehen:
import scala.util.matching.Regex
def recursiveListFiles(f: File, r: Regex): Array[File] = {
val these = f.listFiles
val good = these.filter(f => r.findFirstIn(f.getName).isDefined)
good ++ these.filter(_.isDirectory).flatMap(recursiveListFiles(_,r))
}
listFiles
zurück, null
wenn f
nicht auf ein Verzeichnis verwiesen wird oder wenn ein E / A-Fehler vorliegt (zumindest gemäß der Java-Spezifikation). Das Hinzufügen einer Nullprüfung ist wahrscheinlich für die Verwendung in der Produktion sinnvoll.
f.isDirectory
wahr zurückzukehren , aber f.listFiles
zurückzukehren null
. Wenn Sie beispielsweise keine Berechtigung zum Lesen der Dateien haben, erhalten Sie eine null
. Anstatt beide Prüfungen durchzuführen, würde ich nur die eine Nullprüfung hinzufügen.
f.listFiles
null null zurückgegeben wird !f.isDirectory
.
Ich würde eine Lösung mit Streams bevorzugen, da Sie über ein unendliches Dateisystem iterieren können (Streams sind faul bewertete Sammlungen).
import scala.collection.JavaConversions._
def getFileTree(f: File): Stream[File] =
f #:: (if (f.isDirectory) f.listFiles().toStream.flatMap(getFileTree)
else Stream.empty)
Beispiel für die Suche
getFileTree(new File("c:\\main_dir")).filter(_.getName.endsWith(".scala")).foreach(println)
def getFileTree(f: File): Stream[File] = f #:: Option(f.listFiles()).toStream.flatten.flatMap(getFileTree)
Ab Java 1.7 sollten Sie alle java.nio verwenden. Es bietet nahezu native Leistung (java.io ist sehr langsam) und verfügt über einige nützliche Helfer
Java 1.8 bietet jedoch genau das, wonach Sie suchen:
import java.nio.file.{FileSystems, Files}
import scala.collection.JavaConverters._
val dir = FileSystems.getDefault.getPath("/some/path/here")
Files.walk(dir).iterator().asScala.filter(Files.isRegularFile(_)).foreach(println)
Sie haben auch nach Dateivergleich gefragt. Versuchen Sie java.nio.file.Files.find
und auchjava.nio.file.Files.newDirectoryStream
Siehe Dokumentation hier: http://docs.oracle.com/javase/tutorial/essential/io/walk.html
for (file <- new File("c:\\").listFiles) { processFile(file) }
Scala ist eine Multi-Paradigmen-Sprache. Eine gute "scala-artige" Art, ein Verzeichnis zu iterieren, wäre die Wiederverwendung eines vorhandenen Codes!
Ich würde Commons-io als eine perfekt skala-artige Methode zum Iterieren eines Verzeichnisses betrachten. Sie können einige implizite Konvertierungen verwenden, um dies zu vereinfachen. Mögen
import org.apache.commons.io.filefilter.IOFileFilter
implicit def newIOFileFilter (filter: File=>Boolean) = new IOFileFilter {
def accept (file: File) = filter (file)
def accept (dir: File, name: String) = filter (new java.io.File (dir, name))
}
Ich mag Yuras Stream-Lösung, aber sie (und die anderen) rekursiv in versteckte Verzeichnisse. Wir können auch vereinfachen, indem wir die Tatsache nutzen, dass listFiles
für ein Nicht-Verzeichnis null zurückgegeben wird.
def tree(root: File, skipHidden: Boolean = false): Stream[File] =
if (!root.exists || (skipHidden && root.isHidden)) Stream.empty
else root #:: (
root.listFiles match {
case null => Stream.empty
case files => files.toStream.flatMap(tree(_, skipHidden))
})
Jetzt können wir Dateien auflisten
tree(new File(".")).filter(f => f.isFile && f.getName.endsWith(".html")).foreach(println)
oder realisieren Sie den gesamten Stream für die spätere Verarbeitung
tree(new File("dir"), true).toArray
Die FileUtils von Apache Commons Io passen in eine Zeile und sind gut lesbar:
import scala.collection.JavaConversions._ // important for 'foreach'
import org.apache.commons.io.FileUtils
FileUtils.listFiles(new File("c:\temp"), Array("foo"), true).foreach{ f =>
}
Bisher hat noch niemand https://github.com/pathikrit/better-files erwähnt
val dir = "src"/"test"
val matches: Iterator[File] = dir.glob("**/*.{java,scala}")
// above code is equivalent to:
dir.listRecursively.filter(f => f.extension ==
Some(".java") || f.extension == Some(".scala"))
Schauen Sie sich scala.tools.nsc.io an
Dort gibt es einige sehr nützliche Dienstprogramme, einschließlich Deep Listing-Funktionen für die Directory-Klasse.
Wenn ich mich richtig erinnere, wurde dies von retronym hervorgehoben (möglicherweise beigesteuert) und als Notlösung angesehen, bevor io eine neue und vollständigere Implementierung in der Standardbibliothek erhält.
Und hier ist eine Mischung der Stream-Lösung von @DuncanMcGregor mit dem Filter von @ Rick-777:
def tree( root: File, descendCheck: File => Boolean = { _ => true } ): Stream[File] = {
require(root != null)
def directoryEntries(f: File) = for {
direntries <- Option(f.list).toStream
d <- direntries
} yield new File(f, d)
val shouldDescend = root.isDirectory && descendCheck(root)
( root.exists, shouldDescend ) match {
case ( false, _) => Stream.Empty
case ( true, true ) => root #:: ( directoryEntries(root) flatMap { tree( _, descendCheck ) } )
case ( true, false) => Stream( root )
}
}
def treeIgnoringHiddenFilesAndDirectories( root: File ) = tree( root, { !_.isHidden } ) filter { !_.isHidden }
Auf diese Weise erhalten Sie einen Stream [Datei] anstelle einer (möglicherweise großen und sehr langsamen) Liste [Datei], während Sie entscheiden können, in welche Arten von Verzeichnissen Sie mit der Funktion descCheck () zurückkehren möchten.
Wie wäre es mit
def allFiles(path:File):List[File]=
{
val parts=path.listFiles.toList.partition(_.isDirectory)
parts._2 ::: parts._1.flatMap(allFiles)
}
Ich persönlich mag die Eleganz und Einfachheit der von @Rex Kerr vorgeschlagenen Lösung. Aber so könnte eine rekursive Schwanzversion aussehen:
def listFiles(file: File): List[File] = {
@tailrec
def listFiles(files: List[File], result: List[File]): List[File] = files match {
case Nil => result
case head :: tail if head.isDirectory =>
listFiles(Option(head.listFiles).map(_.toList ::: tail).getOrElse(tail), result)
case head :: tail if head.isFile =>
listFiles(tail, head :: result)
}
listFiles(List(file), Nil)
}
Hier ist eine ähnliche Lösung wie bei Rex Kerr, jedoch mit einem Dateifilter:
import java.io.File
def findFiles(fileFilter: (File) => Boolean = (f) => true)(f: File): List[File] = {
val ss = f.list()
val list = if (ss == null) {
Nil
} else {
ss.toList.sorted
}
val visible = list.filter(_.charAt(0) != '.')
val these = visible.map(new File(f, _))
these.filter(fileFilter) ++ these.filter(_.isDirectory).flatMap(findFiles(fileFilter))
}
Die Methode gibt eine Liste [Datei] zurück, was etwas praktischer ist als Array [Datei]. Außerdem werden alle Verstecke ignoriert, die ausgeblendet sind (dh mit '.' Beginnen).
Es wird teilweise mithilfe eines Dateifilters Ihrer Wahl angewendet, zum Beispiel:
val srcDir = new File( ... )
val htmlFiles = findFiles( _.getName endsWith ".html" )( srcDir )
Die einfachste Nur-Scala-Lösung (wenn es Ihnen nichts ausmacht, die Scala-Compiler-Bibliothek zu benötigen):
val path = scala.reflect.io.Path(dir)
scala.tools.nsc.io.Path.onlyFiles(path.walk).foreach(println)
Ansonsten ist die Lösung von @ Renaud kurz und bündig (wenn es Ihnen nichts ausmacht, Apache Commons FileUtils einzubringen):
import scala.collection.JavaConversions._ // enables foreach
import org.apache.commons.io.FileUtils
FileUtils.listFiles(dir, null, true).foreach(println)
Wo dir
ist eine java.io.File:
new File("path/to/dir")
Es scheint, dass niemand die scala-io
Bibliothek von Scala-Inkubator erwähnt ...
import scalax.file.Path
Path.fromString("c:\temp") ** "a*.foo"
Oder mit implicit
import scalax.file.ImplicitConversions.string2path
"c:\temp" ** "a*.foo"
Oder wenn Sie implicit
explizit wollen ...
import scalax.file.Path
import scalax.file.ImplicitConversions.string2path
val dir: Path = "c:\temp"
dir ** "a*.foo"
Die Dokumentation finden Sie hier: http://jesseeichar.github.io/scala-io-doc/0.4.3/index.html#!/file/glob_based_path_sets
Diese Beschwörung funktioniert bei mir:
def findFiles(dir: File, criterion: (File) => Boolean): Seq[File] = {
if (dir.isFile) Seq()
else {
val (files, dirs) = dir.listFiles.partition(_.isFile)
files.filter(criterion) ++ dirs.toSeq.map(findFiles(_, criterion)).foldLeft(Seq[File]())(_ ++ _)
}
}
Sie können die Schwanzrekursion dafür verwenden:
object DirectoryTraversal {
import java.io._
def main(args: Array[String]) {
val dir = new File("C:/Windows")
val files = scan(dir)
val out = new PrintWriter(new File("out.txt"))
files foreach { file =>
out.println(file)
}
out.flush()
out.close()
}
def scan(file: File): List[File] = {
@scala.annotation.tailrec
def sc(acc: List[File], files: List[File]): List[File] = {
files match {
case Nil => acc
case x :: xs => {
x.isDirectory match {
case false => sc(x :: acc, xs)
case true => sc(acc, xs ::: x.listFiles.toList)
}
}
}
}
sc(List(), List(file))
}
}
Warum verwenden Sie Javas Datei anstelle von Scalas AbstractFile?
Mit Scala's AbstractFile ermöglicht die Iterator-Unterstützung das Schreiben einer präziseren Version von James Moores Lösung:
import scala.reflect.io.AbstractFile
def tree(root: AbstractFile, descendCheck: AbstractFile => Boolean = {_=>true}): Stream[AbstractFile] =
if (root == null || !root.exists) Stream.empty
else
(root.exists, root.isDirectory && descendCheck(root)) match {
case (false, _) => Stream.empty
case (true, true) => root #:: root.iterator.flatMap { tree(_, descendCheck) }.toStream
case (true, false) => Stream(root)
}