software - ¿Cómo enumero todos los archivos en un subdirectorio en scala?
scala tutorial (19)
¿Existe una buena forma "scala-esque" (supongo que me refiero a funcional) de enumerar recursivamente los archivos en un directorio? ¿Qué hay de coincidir con un patrón en particular?
Por ejemplo, recursivamente todos los archivos que coincidan con "a*.foo"
en c:/temp
.
¿Por qué estás usando el archivo de Java en lugar del AbstractFile de Scala?
Con AbstractFile de Scala, el soporte del iterador permite escribir una versión más concisa de la solución de 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)
}
A partir de Java 1.7, todos deberían usar java.nio. Ofrece un rendimiento casi nativo (java.io es muy lento) y tiene algunos ayudantes útiles
Pero Java 1.8 presenta exactamente lo que estás buscando:
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)
También solicitó la coincidencia de archivos. Pruebe java.nio.file.Files.find
y también java.nio.file.Files.newDirectoryStream
Consulte la documentación aquí: http://docs.oracle.com/javase/tutorial/essential/io/walk.html
Aquí hay una solución similar a la de Rex Kerr, pero que incorpora un filtro de archivos:
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))
}
El método devuelve una Lista [Archivo], que es ligeramente más conveniente que la Matriz [Archivo]. También ignora todos los directorios que están ocultos (es decir, que comienzan con ''.'').
Se aplica parcialmente utilizando un filtro de archivos de su elección, por ejemplo:
val srcDir = new File( ... )
val htmlFiles = findFiles( _.getName endsWith ".html" )( srcDir )
Eche un vistazo a scala.tools.nsc.io
Hay algunas utilidades muy útiles que incluyen la funcionalidad de listado profundo en la clase Directory.
Si no recuerdo mal, esto fue resaltado (posiblemente contribuido) por retronym y se consideró como una solución provisional antes de que io obtenga una implementación nueva y más completa en la biblioteca estándar.
El FileUtils Apache Commons Io encaja en una línea y es bastante legible:
import scala.collection.JavaConversions._ // important for ''foreach''
import org.apache.commons.io.FileUtils
FileUtils.listFiles(new File("c:/temp"), Array("foo"), true).foreach{ f =>
}
El código Scala típicamente usa clases Java para manejar E / S, incluyendo la lectura de directorios. Entonces tienes que hacer algo como:
import java.io.File
def recursiveListFiles(f: File): Array[File] = {
val these = f.listFiles
these ++ these.filter(_.isDirectory).flatMap(recursiveListFiles)
}
Puede recopilar todos los archivos y luego filtrar utilizando una expresión regular:
myBigFileArray.filter(f => """.*/.html$""".r.findFirstIn(f.getName).isDefined)
O podría incorporar la expresión regular en la búsqueda recursiva:
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))
}
Este encantamiento funciona para mí:
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]())(_ ++ _)
}
}
La solución más simple de Scala (si no te importa solicitar la biblioteca del compilador de Scala):
val path = scala.reflect.io.Path(dir)
scala.tools.nsc.io.Path.onlyFiles(path.walk).foreach(println)
De lo contrario, la solución de @ Renaud es corta y dulce (si no te importa tirar en Apache Commons FileUtils):
import scala.collection.JavaConversions._ // enables foreach
import org.apache.commons.io.FileUtils
FileUtils.listFiles(dir, null, true).foreach(println)
Donde dir
es un archivo java.io.File:
new File("path/to/dir")
Me gusta la solución de flujo de yura, pero ésta (y las demás) recurre a directorios ocultos. También podemos simplificar haciendo uso del hecho de que listFiles
devuelve null para un no-directorio.
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))
})
Ahora podemos listar archivos
tree(new File(".")).filter(f => f.isFile && f.getName.endsWith(".html")).foreach(println)
o darse cuenta de todo el flujo para su posterior procesamiento
tree(new File("dir"), true).toArray
Nadie ha mencionado aún 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"))
Parece que nadie menciona la biblioteca scala-io
de 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 si quieres implicit
explícitamente ...
import scalax.file.Path
import scalax.file.ImplicitConversions.string2path
val dir: Path = "c:/temp"
dir ** "a*.foo"
La documentación está disponible aquí: http://jesseeichar.github.io/scala-io-doc/0.4.3/index.html#!/file/glob_based_path_sets
Personalmente me gusta la elegancia y la simplicidad de la solución propuesta de @Rex Kerr. Pero aquí está lo que podría ser una versión recursiva de cola:
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)
}
Preferiría la solución con Streams porque puede iterar sobre un sistema de archivos infinito (las secuencias son colecciones evaluadas de forma diferida)
import scala.collection.JavaConversions._
def getFileTree(f: File): Stream[File] =
f #:: (if (f.isDirectory) f.listFiles().toStream.flatMap(getFileTree)
else Stream.empty)
Ejemplo para buscar
getFileTree(new File("c://main_dir")).filter(_.getName.endsWith(".scala")).foreach(println)
Puede usar recursividad de cola para ello:
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))
}
}
Qué tal si
def allFiles(path:File):List[File]=
{
val parts=path.listFiles.toList.partition(_.isDirectory)
parts._2 ::: parts._1.flatMap(allFiles)
}
Scala es un lenguaje multi-paradigma. Una buena forma de escalar un directorio sería reutilizar un código existente.
Consideraría usar commons-io, una forma perfectamente escalar de iterar un directorio. Puede usar algunas conversiones implícitas para hacerlo más fácil. Me gusta
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))
}
Scala tiene la biblioteca ''scala.reflect.io'' que considera experimental pero hace el trabajo
import scala.reflect.io.Path
Path(path) walkFilter { p =>
p.isDirectory || """a*.foo""".r.findFirstIn(p.name).isDefined
}
Y aquí hay una mezcla de la solución de flujo de @DuncanMcGregor con el filtro de @ 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 }
Esto le da una lista de Stream [Archivo] en lugar de una (potencialmente enorme y muy lenta) Lista [Archivo] al mismo tiempo que le permite decidir qué tipo de directorios recurrir en con la función descenCheck ().
for (file <- new File("c://").listFiles) { processFile(file) }