Wie liste ich alle Dateien in einem Unterverzeichnis in Scala auf?

90

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.

Nick Fortescue
quelle

Antworten:

112

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))
}
Rex Kerr
quelle
7
WARNUNG: Ich habe diesen Code ausgeführt und manchmal gibt f.listFiles null zurück (ich weiß nicht warum, aber auf meinem Mac) und die Funktion recursiveListFiles stürzt ab. Ich habe nicht genug Erfahrung, um einen eleganten Null-Check in Scala zu erstellen, aber ein leeres Array zurückzugeben, wenn diese == Null für mich funktioniert haben.
Jan
2
@Jan - Gibt listFileszurück, nullwenn fnicht 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.
Rex Kerr
5
@ Peter Schwarz - Sie noch brauchen die NULL - Prüfung, da es möglich ist , für f.isDirectorywahr zurückzukehren , aber f.listFileszurü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.
Rex Kerr
1
Tatsächlich benötigen Sie nur die Nullprüfung, da bei f.listFilesnull null zurückgegeben wird !f.isDirectory.
Duncan McGregor
2
In Bezug auf die Nullprüfung wäre die idiomatischste Möglichkeit, die Null in eine Option umzuwandeln und eine Karte zu verwenden. Die Zuweisung lautet also val These = Option (f.listFiles) und der ++ - Operator befindet sich in einer Kartenoperation mit einem 'getOrElse' am Ende
oder Peles
47

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)
Yura
quelle
4
Alternative Syntax:def getFileTree(f: File): Stream[File] = f #:: Option(f.listFiles()).toStream.flatten.flatMap(getFileTree)
VasiliNovikov
3
Ich stimme Ihrer Absicht zu, aber dies ist Ihre Lösung sinnlos. listFiles () gibt bereits ein vollständig ausgewertetes Array zurück, das Sie dann bei toStream "träge" auswerten. Sie benötigen einen Stream-Form-Scratch. Suchen Sie nach java.nio.file.DirectoryStream.
Daniel Langdon
7
@ Daniel es ist nicht absolut streng, es rekursiert Verzeichnisse träge.
Guillaume Massé
3
Ich werde das jetzt auf meinem unendlichen Dateisystem versuchen :-)
Brian Agnew
Achtung: JavaConversions ist jetzt veraltet. Verwenden Sie JavaConverters und asScala Decoration Instread.
Suma
25

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.findund auchjava.nio.file.Files.newDirectoryStream

Siehe Dokumentation hier: http://docs.oracle.com/javase/tutorial/essential/io/walk.html

monzonj
quelle
Ich erhalte: Fehler: (38, 32) Wert asScala ist kein Mitglied von java.util.Iterator [java.nio.file.Path] Files.walk (dir) .iterator (). asScala.filter (Files.isRegularFile ( _)). foreach (println)
stuart
20
for (file <- new File("c:\\").listFiles) { processFile(file) }

http://langref.org/scala+java/files

Phil
quelle
17
Dies macht nur eine Ebene; Es wird nicht in die Verzeichnisse in c: \ zurückgeführt.
James Moore
11

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))
}
ArtemGr
quelle
11

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 listFilesfü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
Duncan McGregor
quelle
6

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 =>

}
Renaud
quelle
Ich musste Typinformationen hinzufügen: FileUtils.listFiles (neue Datei ("c: \ temp"), Array ("foo"), true) .toArray (Array [File] ()). Foreach {f =>}
Jason Wheeler
In einem Dateisystem, bei dem zwischen Groß- und Kleinschreibung unterschieden wird, ist dies nicht sehr nützlich, da die mitgelieferten Erweiterungen genau mit Groß- und Kleinschreibung übereinstimmen müssen. Es scheint keine Möglichkeit zu geben, den ExtensionFileComparator anzugeben.
Brent Faust
eine Problemumgehung: Geben Sie Array ("foo", "FOO", "png", "PNG")
Renaud
5

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")) 
Phil
quelle
3

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.

Don Mackenzie
quelle
3

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.

James Moore
quelle
3

Wie wäre es mit

   def allFiles(path:File):List[File]=
   {    
       val parts=path.listFiles.toList.partition(_.isDirectory)
       parts._2 ::: parts._1.flatMap(allFiles)         
   }
Dino Fancellu
quelle
3

Scala hat die Bibliothek 'scala.reflect.io', die als experimentell angesehen wurde, aber die Arbeit erledigt

import scala.reflect.io.Path
Path(path) walkFilter { p => 
  p.isDirectory || """a*.foo""".r.findFirstIn(p.name).isDefined
}
roterl
quelle
3

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)
}
Polbotinka
quelle
Was ist mit Überlauf?
Norisknofun
1

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 )
Rick-777
quelle
1

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 dirist eine java.io.File:

new File("path/to/dir")
Brent Faust
quelle
1

Es scheint, dass niemand die scala-ioBibliothek 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 implicitexplizit 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

zeichnen
quelle
0

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]())(_ ++ _)
    }
  }
Connor Doyle
quelle
0

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))
  }
}
Milind
quelle
-1

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)
    }
Nicolas Rouquette
quelle