Esiste un buon modo "scala-esque" (immagino intendo funzionale) per elencare in modo ricorsivo i file in una directory? Che ne dici di abbinare un modello particolare?
Ad esempio ricorsivamente tutti i file che corrispondono "a*.foo"in c:\temp.
Esiste un buon modo "scala-esque" (immagino intendo funzionale) per elencare in modo ricorsivo i file in una directory? Che ne dici di abbinare un modello particolare?
Ad esempio ricorsivamente tutti i file che corrispondono "a*.foo"in c:\temp.
Risposte:
Il codice Scala in genere utilizza classi Java per gestire l'I / O, inclusa la lettura delle directory. Quindi devi fare qualcosa come:
import java.io.File
def recursiveListFiles(f: File): Array[File] = {
val these = f.listFiles
these ++ these.filter(_.isDirectory).flatMap(recursiveListFiles)
}
Puoi raccogliere tutti i file e quindi filtrare usando un'espressione regolare:
myBigFileArray.filter(f => """.*\.html$""".r.findFirstIn(f.getName).isDefined)
Oppure potresti incorporare la regex nella ricerca ricorsiva:
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))
}
listFilesrestituisce nullse fnon punta a una directory o se c'è un errore di I / O (almeno secondo le specifiche Java). L'aggiunta di un controllo nullo è probabilmente saggio per l'uso in produzione.
f.isDirectoryrestituire true ma f.listFilesrestituire null. Ad esempio, se non hai il permesso di leggere i file, otterrai un null. Piuttosto che avere entrambi i controlli, aggiungerei solo un controllo nullo.
f.listFilesrestituisce null quando !f.isDirectory.
Preferirei una soluzione con Streams perché puoi iterare su un file system infinito (gli stream sono raccolte valutate pigre)
import scala.collection.JavaConversions._
def getFileTree(f: File): Stream[File] =
f #:: (if (f.isDirectory) f.listFiles().toStream.flatMap(getFileTree)
else Stream.empty)
Esempio per la ricerca
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)
A partire da Java 1.7 dovreste tutti usare java.nio. Offre prestazioni quasi native (java.io è molto lento) e ha alcuni utili aiutanti
Ma Java 1.8 introduce esattamente quello che stai cercando:
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)
Hai anche chiesto la corrispondenza dei file. Prova java.nio.file.Files.finde anchejava.nio.file.Files.newDirectoryStream
Consulta la documentazione qui: http://docs.oracle.com/javase/tutorial/essential/io/walk.html
for (file <- new File("c:\\").listFiles) { processFile(file) }
Scala è un linguaggio multi-paradigma. Un buon modo "scala-esque" di iterare una directory sarebbe riutilizzare un codice esistente!
Considererei l' uso di commons-io un modo perfettamente in scala di iterare una directory. Puoi utilizzare alcune conversioni implicite per renderlo più semplice. Piace
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))
}
Mi piace la soluzione di flusso di yura, ma (e le altre) ricorre in directory nascoste. Possiamo anche semplificare utilizzando il fatto che listFilesrestituisce null per una non directory.
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))
})
Ora possiamo elencare i file
tree(new File(".")).filter(f => f.isFile && f.getName.endsWith(".html")).foreach(println)
o realizzare l'intero flusso per l'elaborazione successiva
tree(new File("dir"), true).toArray
FileUtils di Apache Commons Io sta su una riga ed è abbastanza leggibile:
import scala.collection.JavaConversions._ // important for 'foreach'
import org.apache.commons.io.FileUtils
FileUtils.listFiles(new File("c:\temp"), Array("foo"), true).foreach{ f =>
}
Nessuno ha ancora menzionato https://github.com/pathikrit/better-files
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"))
Dai un'occhiata a scala.tools.nsc.io
Ci sono alcune utilità molto utili che includono funzionalità di elenchi approfonditi nella classe Directory.
Se non ricordo male, questo è stato evidenziato (forse contribuito) da retronym ed è stato visto come un tamponamento prima che io ottenga un'implementazione nuova e più completa nella libreria standard.
Ed ecco una miscela della soluzione di flusso di @DuncanMcGregor con il filtro di @ 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 }
Questo ti dà uno Stream [File] invece di un (potenzialmente enorme e molto lento) List [File] mentre ti permette di decidere in quale tipo di directory ricorrere con la funzione descendCheck ().
Che ne dite di
def allFiles(path:File):List[File]=
{
val parts=path.listFiles.toList.partition(_.isDirectory)
parts._2 ::: parts._1.flatMap(allFiles)
}
Personalmente mi piace l'eleganza e la semplicità della soluzione proposta da @Rex Kerr. Ma ecco come potrebbe apparire una versione ricorsiva della coda:
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)
}
Ecco una soluzione simile a quella di Rex Kerr, ma che incorpora un filtro di file:
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))
}
Il metodo restituisce un List [File], che è leggermente più conveniente di Array [File]. Ignora anche tutte le directory nascoste (cioè che iniziano con ".").
Viene applicato parzialmente utilizzando un filtro file di tua scelta, ad esempio:
val srcDir = new File( ... )
val htmlFiles = findFiles( _.getName endsWith ".html" )( srcDir )
La soluzione più semplice per Scala (se non ti dispiace richiedere la libreria del compilatore Scala):
val path = scala.reflect.io.Path(dir)
scala.tools.nsc.io.Path.onlyFiles(path.walk).foreach(println)
Altrimenti, la soluzione di @ Renaud è breve e dolce (se non ti dispiace tirare in Apache Commons FileUtils):
import scala.collection.JavaConversions._ // enables foreach
import org.apache.commons.io.FileUtils
FileUtils.listFiles(dir, null, true).foreach(println)
Dove si dirtrova un java.io.File:
new File("path/to/dir")
Sembra che nessuno citi la scala-iolibreria di scala-incubrator ...
import scalax.file.Path
Path.fromString("c:\temp") ** "a*.foo"
O con implicit
import scalax.file.ImplicitConversions.string2path
"c:\temp" ** "a*.foo"
O se vuoi implicitesplicitamente ...
import scalax.file.Path
import scalax.file.ImplicitConversions.string2path
val dir: Path = "c:\temp"
dir ** "a*.foo"
La documentazione è disponibile qui: http://jesseeichar.github.io/scala-io-doc/0.4.3/index.html#!/file/glob_based_path_sets
Questo incantesimo funziona per me:
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]())(_ ++ _)
}
}
Puoi usare la ricorsione della coda per questo:
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))
}
}
Perché stai usando Java's File invece di Scala's AbstractFile?
Con AbstractFile di Scala, il supporto dell'iteratore consente di scrivere una versione più concisa della soluzione di James Moore:
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)
}