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))
}
listFiles
restituisce null
se f
non 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.isDirectory
restituire true ma f.listFiles
restituire 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.listFiles
restituisce 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.find
e 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 listFiles
restituisce 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 dir
trova un java.io.File:
new File("path/to/dir")
Sembra che nessuno citi la scala-io
libreria 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 implicit
esplicitamente ...
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)
}