Como faço para listar todos os arquivos em um subdiretório no scala?

90

Existe uma boa maneira "scala-esque" (acho que quero dizer funcional) de listar recursivamente os arquivos em um diretório? Que tal combinar um padrão específico?

Por exemplo, recursivamente, todos os arquivos correspondentes "a*.foo"em c:\temp.

Nick Fortescue
fonte

Respostas:

112

O código Scala normalmente usa classes Java para lidar com E / S, incluindo diretórios de leitura. Então você tem que fazer algo como:

import java.io.File
def recursiveListFiles(f: File): Array[File] = {
  val these = f.listFiles
  these ++ these.filter(_.isDirectory).flatMap(recursiveListFiles)
}

Você pode coletar todos os arquivos e filtrar usando um regex:

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

Ou você pode incorporar o regex na pesquisa 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
fonte
7
AVISO: Eu executei este código e às vezes f.listFiles retorna null (não sei por que, mas no meu mac faz) e a função recursiveListFiles falha. Não tenho experiência suficiente para construir uma verificação nula elegante em scala, mas retornando um array vazio se estes == null funcionaram para mim.
Janeiro
2
@Jan - listFilesretorna nullse fnão apontar para um diretório ou se houver um erro de IO (pelo menos de acordo com a especificação Java). Adicionar uma verificação nula é provavelmente uma boa opção para uso em produção.
Rex Kerr
5
@Peter Schwarz - Você ainda precisa da verificação de nulo, já que é possível f.isDirectoryretornar verdadeiro, mas f.listFilesretornar null. Por exemplo, se você não tiver permissão para ler os arquivos, receberá um null. Em vez de ter as duas verificações, eu apenas adicionaria uma verificação nula.
Rex Kerr
1
Na verdade, você só precisa da verificação de nulo, pois f.listFilesretorna nulo quando !f.isDirectory.
Duncan McGregor
2
Com relação à verificação Null, a maneira mais idiomática seria converter o nulo em opção e usar o mapa. Portanto, a atribuição é val these = Option (f.listFiles) e o operador ++ está dentro de uma operação de mapa com um 'getOrElse' no final
Ou Peles
47

Eu preferiria uma solução com Streams porque você pode iterar em um sistema de arquivos infinito (Streams são coleções avaliadas lentamente)

import scala.collection.JavaConversions._

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

Exemplo para pesquisar

getFileTree(new File("c:\\main_dir")).filter(_.getName.endsWith(".scala")).foreach(println)
yura
fonte
4
Sintaxe alternativa:def getFileTree(f: File): Stream[File] = f #:: Option(f.listFiles()).toStream.flatten.flatMap(getFileTree)
VasiliNovikov
3
Eu concordo com sua intenção, mas esta sua solução é inútil. listFiles () já retorna um array totalmente avaliado, que você então avalia "preguiçosamente" em toStream. Você precisa de um rascunho de formulário de fluxo, procure java.nio.file.DirectoryStream.
Daniel Langdon
7
@Daniel não é absolutamente estrito, ele recorre os diretórios preguiçosamente.
Guillaume Massé
3
Vou tentar isso agora mesmo no meu sistema de arquivos infinito :-)
Brian Agnew
Cuidado: JavaConversions agora está obsoleto. Use JavaConverters e instruções de decoração asScala.
Suma
25

A partir do Java 1.7, todos devem estar usando java.nio. Ele oferece desempenho próximo ao nativo (java.io é muito lento) e tem alguns auxiliares úteis

Mas o Java 1.8 apresenta exatamente o que você está procurando:

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)

Você também solicitou a correspondência de arquivos. Experimente java.nio.file.Files.finde tambémjava.nio.file.Files.newDirectoryStream

Veja a documentação aqui: http://docs.oracle.com/javase/tutorial/essential/io/walk.html

Monzonj
fonte
eu obtenho: Erro: (38, 32) o valor asScala não é membro de 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
fonte
17
Isso faz apenas um nível; ele não retorna aos diretórios em c: \.
James Moore
11

Scala é uma linguagem multiparadigma. Uma boa maneira "scala-esque" de iterar um diretório seria reutilizar um código existente!

Eu consideraria o uso de commons-io uma maneira perfeitamente scala-esque de iterar um diretório. Você pode usar algumas conversões implícitas para tornar mais fácil. Gostar

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
fonte
11

Eu gosto da solução de stream do yura, mas ela (e as outras) recorre para diretórios ocultos. Também podemos simplificar, utilizando o fato de que listFilesretorna null para um não diretório.

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))
  })

Agora podemos listar arquivos

tree(new File(".")).filter(f => f.isFile && f.getName.endsWith(".html")).foreach(println)

ou realizar todo o fluxo para processamento posterior

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

O FileUtils do Apache Commons Io cabe em uma linha e é bastante legível:

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
fonte
Tive que adicionar informações de tipo: FileUtils.listFiles (new File ("c: \ temp"), Array ("foo"), true) .toArray (Array [File] ()). Foreach {f =>}
Jason Wheeler
Não é muito útil em um sistema de arquivos com distinção entre maiúsculas e minúsculas, pois as extensões fornecidas devem corresponder exatamente às maiúsculas e minúsculas. Não parece haver uma maneira de especificar o ExtensionFileComparator.
Brent Faust
uma solução alternativa: fornecer Array ("foo", "FOO", "png", "PNG")
Renaud
5

Ninguém mencionou ainda 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
fonte
3

Dê uma olhada em scala.tools.nsc.io

Existem alguns utilitários muito úteis lá, incluindo funcionalidade de listagem detalhada na classe Directory.

Se bem me lembro, isso foi destacado (possivelmente contribuído) por retronímia e foi visto como um paliativo antes que o io obtenha uma implementação nova e mais completa na biblioteca padrão.

Don Mackenzie
fonte
3

E aqui está uma mistura da solução de fluxo de @DuncanMcGregor com o 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 }

Isso dá a você um Stream [Arquivo] em vez de uma (potencialmente grande e muito lenta) Lista [Arquivo], enquanto permite que você decida quais tipos de diretórios serão recursivamente com a função descendCheck ().

James Moore
fonte
3

E se

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

Scala tem a biblioteca 'scala.reflect.io' que é considerada experimental, mas faz o trabalho

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

Eu pessoalmente gosto da elegância e simplicidade da solução proposta por @Rex Kerr. Mas aqui está a aparência de uma versão recursiva de cauda:

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
fonte
que tal estouro?
norisknofun
1

Aqui está uma solução semelhante à de Rex Kerr, mas incorporando um filtro de arquivo:

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))
}

O método retorna um List [File], que é um pouco mais conveniente do que Array [File]. Ele também ignora todos os diretórios que estão ocultos (ou seja, começando com '.').

É parcialmente aplicado usando um filtro de arquivo de sua escolha, por exemplo:

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

A solução mais simples somente Scala (se você não se importa em exigir a biblioteca do compilador Scala):

val path = scala.reflect.io.Path(dir)
scala.tools.nsc.io.Path.onlyFiles(path.walk).foreach(println)

Caso contrário, a solução de @Reaud é curta e agradável (se você não se importar em usar o Apache Commons FileUtils):

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

Onde direstá um java.io.File:

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

Parece que ninguém menciona a scala-iobiblioteca do scala-incubrator ...

import scalax.file.Path

Path.fromString("c:\temp") ** "a*.foo"

Ou com implicit

import scalax.file.ImplicitConversions.string2path

"c:\temp" ** "a*.foo"

Ou se você quiser implicitexplicitamente ...

import scalax.file.Path
import scalax.file.ImplicitConversions.string2path

val dir: Path = "c:\temp"
dir ** "a*.foo"

A documentação está disponível aqui: http://jesseeichar.github.io/scala-io-doc/0.4.3/index.html#!/file/glob_based_path_sets

desenhar
fonte
0

Este encantamento funciona para mim:

  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
fonte
0

Você pode usar recursão de cauda para isso:

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
fonte
-1

Por que você está usando o arquivo Java em vez do AbstractFile do Scala?

Com o AbstractFile da Scala, o suporte ao iterador permite escrever uma versão mais concisa da solução 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
fonte