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.

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

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)

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


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


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

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

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 =>

}

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

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.


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 ().


3

E se

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

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
}

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

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 )

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

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


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]())(_ ++ _)
    }
  }

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

-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)
    }
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.