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.
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:
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))
}
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.
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.
f.listFilesretorna nulo quando !f.isDirectory.
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)
def getFileTree(f: File): Stream[File] = f #:: Option(f.listFiles()).toStream.flatten.flatMap(getFileTree)
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
for (file <- new File("c:\\").listFiles) { processFile(file) }
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))
}
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
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 =>
}
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"))
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.
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 ().
E se
def allFiles(path:File):List[File]=
{
val parts=path.listFiles.toList.partition(_.isDirectory)
parts._2 ::: parts._1.flatMap(allFiles)
}
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)
}
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 )
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")
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
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]())(_ ++ _)
}
}
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))
}
}
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)
}