¿Cómo enumero todos los archivos en un subdirectorio en scala?

90

¿Existe una buena forma "scala-esque" (supongo que me refiero a funcional) de enumerar archivos de forma recursiva en un directorio? ¿Qué hay de hacer coincidir un patrón en particular?

Por ejemplo, de forma recursiva todos los archivos que coinciden "a*.foo"en c:\temp.

Nick Fortescue
fuente

Respuestas:

112

El código de Scala generalmente usa clases de Java para tratar con E / S, incluida 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 usando una expresión regular:

myBigFileArray.filter(f => """.*\.html$""".r.findFirstIn(f.getName).isDefined)

O puede 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))
}
Rex Kerr
fuente
7
ADVERTENCIA: Ejecuté este código y, a veces, f.listFiles devuelve nulo (no sé por qué, pero en mi mac lo hace) y la función recursiveListFiles se bloquea. No tengo la experiencia suficiente para construir una verificación nula elegante en scala, pero devuelvo una matriz vacía si estos == null funcionaron para mí.
enero
2
@Jan: listFilesdevuelve nullsi fno apunta a un directorio o si hay un error de IO (al menos de acuerdo con las especificaciones de Java). Agregar un cheque nulo probablemente sea conveniente para el uso de producción.
Rex Kerr
5
@Peter Schwarz: aún necesita la verificación nula, ya que es posible f.isDirectorydevolver verdadero pero f.listFilesregresar null. Por ejemplo, si no tiene permiso para leer los archivos, obtendrá un null. En lugar de tener ambos controles, simplemente agregaría el único control nulo.
Rex Kerr
1
De hecho, solo necesita la verificación nula, ya que f.listFilesdevuelve nulo cuando !f.isDirectory.
Duncan McGregor
2
Con respecto a la verificación Null, la forma más idiomática sería convertir el nulo en opción y usar map. Entonces la asignación es val these = Option (f.listFiles) y el operador ++ está dentro de una operación de mapa con un 'getOrElse' al final
O Peles
47

Preferiría una solución con Streams porque puede iterar sobre un sistema de archivos infinito (los Streams son colecciones evaluadas de forma perezosa)

import scala.collection.JavaConversions._

def getFileTree(f: File): Stream[File] =
        f #:: (if (f.isDirectory) f.listFiles().toStream.flatMap(getFileTree) 
               else Stream.empty)

Ejemplo de búsqueda

getFileTree(new File("c:\\main_dir")).filter(_.getName.endsWith(".scala")).foreach(println)
yura
fuente
4
Sintaxis alternativa:def getFileTree(f: File): Stream[File] = f #:: Option(f.listFiles()).toStream.flatten.flatMap(getFileTree)
VasiliNovikov
3
Estoy de acuerdo con tu intención, pero esta tu solución no tiene sentido. listFiles () ya devuelve una matriz completamente evaluada, que luego evalúa "perezosamente" en toStream. Necesita un formulario de flujo desde cero, busque java.nio.file.DirectoryStream.
Daniel Langdon
7
@Daniel no es absolutamente estricto, recurre a directorios perezosamente.
Guillaume Massé
3
Lo intentaré ahora mismo en mi sistema de archivos infinito :-)
Brian Agnew
Cuidado: JavaConversions ahora está en desuso. Utilice JavaConverters y la decoración asScala incorporada.
Suma
25

A partir de Java 1.7, todos deberían usar java.nio. Ofrece un rendimiento cercano al nativo (java.io es muy lento) y tiene algunos ayudantes útiles

Pero Java 1.8 presenta exactamente lo que está 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. Prueba java.nio.file.Files.findy tambiénjava.nio.file.Files.newDirectoryStream

Consulte la documentación aquí: http://docs.oracle.com/javase/tutorial/essential/io/walk.html

monzonj
fuente
obtengo: Error: (38, 32) valor asScala no es miembro de java.util.Iterator [java.nio.file.Path] Files.walk (dir) .iterator (). asScala.filter (Files.isRegularFile ( _)). foreach (println)
stuart
11

Scala es un lenguaje de múltiples paradigmas. Una buena forma "scala-esque" de iterar un directorio sería reutilizar un código existente.

Consideraría usar commons-io como una forma perfectamente escalada 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))
}
ArtemGr
fuente
11

Me gusta la solución de flujo de yura, pero (y las demás) recurre a directorios ocultos. También podemos simplificar haciendo uso del hecho de que listFilesdevuelve nulo 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 realizar todo el flujo para su posterior procesamiento

tree(new File("dir"), true).toArray
Duncan McGregor
fuente
6

FileUtils de Apache Commons Io cabe 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 =>

}
Renaud
fuente
Tuve que agregar información de tipo: FileUtils.listFiles (new File ("c: \ temp"), Array ("foo"), true) .toArray (Array [File] ()). Foreach {f =>}
Jason Wheeler
No es muy útil en un sistema de archivos que distingue entre mayúsculas y minúsculas, ya que las extensiones suministradas deben coincidir exactamente con las mayúsculas y minúsculas. No parece haber una forma de especificar ExtensionFileComparator.
Brent Faust
una solución alternativa: proporcione Array ("foo", "FOO", "png", "PNG")
Renaud
5

Nadie ha mencionado todavía 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")) 
Phil
fuente
3

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 mal no recuerdo, esto fue resaltado (posiblemente contribuido) por retronym y fue visto como una solución provisional antes de que io obtenga una implementación nueva y más completa en la biblioteca estándar.

Don Mackenzie
fuente
3

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 un [Archivo] de Flujo en lugar de un [Archivo] Lista (potencialmente enorme y muy lento) mientras le permite decidir en qué tipo de directorios recurrir con la función descenCheck ().

James Moore
fuente
3

Qué tal si

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

Scala tiene la biblioteca 'scala.reflect.io' que se considera experimental pero hace el trabajo

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

Personalmente, me gusta la elegancia y simplicidad de la solución propuesta por @Rex Kerr. Pero así es como se vería 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)
}
polbotinka
fuente
¿qué pasa con el desbordamiento?
norisknofun
1

Aquí hay una solución similar a la de Rex Kerr, pero que incorpora un filtro de archivo:

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 un List [File], que es un poco más conveniente que Array [File]. También ignora todos los directorios que están ocultos (es decir, que comienzan con '.').

Se aplica parcialmente usando un filtro de archivo de su elección, por ejemplo:

val srcDir = new File( ... )
val htmlFiles = findFiles( _.getName endsWith ".html" )( srcDir )
Rick-777
fuente
1

La solución más simple solo para Scala (si no le importa requerir 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 breve y sencilla (si no le importa extraer Apache Commons FileUtils):

import scala.collection.JavaConversions._  // enables foreach
import org.apache.commons.io.FileUtils
FileUtils.listFiles(dir, null, true).foreach(println)

¿Dónde direstá un archivo java.io.:

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

Parece que nadie menciona la scala-iobiblioteca 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 implicitexplí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

dibujar
fuente
0

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

Puede usar la 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))
  }
}
Milind
fuente
-1

¿Por qué está utilizando 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)
    }
Nicolas Rouquette
fuente