Как мне перечислить все файлы в подкаталоге в Scala?
Есть ли хороший "scala-esque" (я думаю, я имею в виду функциональный) способ рекурсивного перечисления файлов в каталоге? Как насчет соответствия определенного шаблона?
Например, рекурсивно все файлы, соответствующие "a*.foo"
в c:\temp
,
23 ответа
Код Scala обычно использует классы Java для работы с вводом-выводом, включая чтение каталогов. Так что вы должны сделать что-то вроде:
import java.io.File
def recursiveListFiles(f: File): Array[File] = {
val these = f.listFiles
these ++ these.filter(_.isDirectory).flatMap(recursiveListFiles)
}
Вы можете собрать все файлы и затем отфильтровать, используя регулярное выражение:
myBigFileArray.filter(f => """.*\.html$""".r.findFirstIn(f.getName).isDefined)
Или вы можете включить регулярное выражение в рекурсивный поиск:
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))
}
Я бы предпочел решение с потоками, потому что вы можете перебирать бесконечную файловую систему (потоки - это ленивые оцененные коллекции)
import scala.collection.JavaConversions._
def getFileTree(f: File): Stream[File] =
f #:: (if (f.isDirectory) f.listFiles().toStream.flatMap(getFileTree)
else Stream.empty)
Пример для поиска
getFileTree(new File("c:\\main_dir")).filter(_.getName.endsWith(".scala")).foreach(println)
Начиная с Java 1.7 вы все должны использовать java.nio. Он предлагает производительность, близкую к нативной (java.io очень медленный) и имеет несколько полезных помощников.
Но Java 1.8 представляет именно то, что вы ищете:
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)
Вы также попросили сопоставить файл. Пытаться java.nio.file.Files.find
а также java.nio.file.Files.newDirectoryStream
См. Документацию здесь: http://docs.oracle.com/javase/tutorial/essential/io/walk.html
for (file <- new File("c:\\").listFiles) { processFile(file) }
Scala - это мультипарадигмальный язык. Хороший способ перебора каталога в стиле scala-esque - это повторное использование существующего кода!
Я бы посоветовал использовать commons-io в идеальном скалируемом способе итерации каталога. Вы можете использовать некоторые неявные преобразования, чтобы сделать это проще. подобно
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))
}
Мне нравится потоковое решение Юры, но оно (и другие) возвращается в скрытые каталоги. Мы также можем упростить, используя тот факт, что listFiles
возвращает ноль для не-каталога.
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))
})
Теперь мы можем перечислить файлы
tree(new File(".")).filter(f => f.isFile && f.getName.endsWith(".html")).foreach(println)
или реализовать весь поток для последующей обработки
tree(new File("dir"), true).toArray
Никто еще не упомянул 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"))
FileUtils Apache Commons Io помещается в одну строку и вполне читабелен:
import scala.collection.JavaConversions._ // important for 'foreach'
import org.apache.commons.io.FileUtils
FileUtils.listFiles(new File("c:\temp"), Array("foo"), true).foreach{ f =>
}
Мне лично нравится элегантность и простота предложенного решения @Rex Kerr. Но вот как может выглядеть хвостовая рекурсивная версия:
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)
}
А вот смесь потокового решения из @DuncanMcGregor с фильтром из @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 }
Это дает вам Stream[File] вместо (потенциально огромного и очень медленного) List[File], в то же время позволяя вам решить, какие каталоги использовать в рекурсивной функции с помощью функцией yieldndCheck().
Как насчет
def allFiles(path:File):List[File]=
{
val parts=path.listFiles.toList.partition(_.isDirectory)
parts._2 ::: parts._1.flatMap(allFiles)
}
Самое простое решение только для Scala (если вы не возражаете против использования библиотеки компилятора Scala):
val path = scala.reflect.io.Path(dir)
scala.tools.nsc.io.Path.onlyFiles(path.walk).foreach(println)
В противном случае решение @Renaud будет коротким и приятным (если вы не против использовать Apache Commons FileUtils):
import scala.collection.JavaConversions._ // enables foreach
import org.apache.commons.io.FileUtils
FileUtils.listFiles(dir, null, true).foreach(println)
куда dir
такое java.io.File:
new File("path/to/dir")
Взгляните на scala.tools.nsc.io
Там есть несколько очень полезных утилит, включая функцию глубокого перечисления в классе Directory.
Если я правильно помню, это было выделено (возможно, внесено) ретронимом и воспринималось как пробел перед тем, как io получит свежую и более полную реализацию в стандартной библиотеке.
В Scala есть библиотека scala.reflect.io, которая считается экспериментальной, но выполняет работу
import scala.reflect.io.Path
Path(path) walkFilter { p =>
p.isDirectory || """a*.foo""".r.findFirstIn(p.name).isDefined
}
os-lib - это самый простой способ рекурсивного перечисления файлов в Scala.
os.walk(os.pwd/"countries").filter(os.isFile(_))
Вот как рекурсивно перечислить все файлы, соответствующие
"a*.foo"
шаблон, указанный в вопросе:
os.walk(os.pwd/"countries").filter(_.segments.toList.last matches "a.*\\.foo")
os-lib намного элегантнее и мощнее, чем другие альтернативы. Он возвращается
os
объекты, которые можно легко перемещать, переименовывать и т. д. Вам больше не нужно мучиться с громоздкими библиотеками Java.
Вот фрагмент кода, который вы можете запустить, если хотите поэкспериментировать с этой библиотекой на своем локальном компьютере:
os.makeDir(os.pwd/"countries")
os.makeDir(os.pwd/"countries"/"colombia")
os.write(os.pwd/"countries"/"colombia"/"medellin.txt", "q mas pues")
os.write(os.pwd/"countries"/"colombia"/"a_something.foo", "soy un rolo")
os.makeDir(os.pwd/"countries"/"brasil")
os.write(os.pwd/"countries"/"brasil"/"a_whatever.foo", "carnaval")
os.write(os.pwd/"countries"/"brasil"/"a_city.txt", "carnaval")
println(os.walk(os.pwd/"countries").filter(os.isFile(_)))
вернет это:
ArraySeq(
/.../countries/brasil/a_whatever.foo,
/.../countries/brasil/a_city.txt,
/.../countries/colombia/a_something.foo,
/.../countries/colombia/medellin.txt)
os.walk(os.pwd/"countries").filter(_.segments.toList.last matches "a.*\\.foo")
вернет это:
ArraySeq(
/.../countries/brasil/a_whatever.foo,
/.../countries/colombia/a_something.foo)
Смотрите здесь для более подробной информации о том , как использовать ОСА-Lib.
Кажется, никто не упоминает scala-io
библиотека из скала-инкубатора...
import scalax.file.Path
Path.fromString("c:\temp") ** "a*.foo"
Или с implicit
import scalax.file.ImplicitConversions.string2path
"c:\temp" ** "a*.foo"
Или если вы хотите implicit
явно...
import scalax.file.Path
import scalax.file.ImplicitConversions.string2path
val dir: Path = "c:\temp"
dir ** "a*.foo"
Документация доступна здесь: http://jesseeichar.github.io/scala-io-doc/0.4.3/index.html
Метод deepFiles в scala.reflect.io.Directory предоставляет довольно хороший способ рекурсивного получения всех файлов в каталоге:
import scala.reflect.io.Directory
new Directory(f).deepFiles.filter(x => x.startsWith("a") && x.endsWith(".foo"))
deepFiles возвращает итератор, поэтому вы можете преобразовать его в какой-либо другой тип коллекции, если вам не нужна / не нужна ленивая оценка.
Вот решение, похожее на решение Rex Kerr, но включающее файловый фильтр:
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))
}
Метод возвращает List[File], что несколько удобнее, чем Array[File]. Также игнорируются все скрытые каталоги (т. Е. Начинающиеся с '.').
Он частично применяется с использованием фильтра файлов по вашему выбору, например:
val srcDir = new File( ... )
val htmlFiles = findFiles( _.getName endsWith ".html" )( srcDir )
Это заклинание работает для меня:
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]())(_ ++ _)
}
}
Незначительное улучшение принятого ответа.
Разделив на
_.isDirectory
эта функция возвращает только список файлов.
(каталоги исключены)
import java.io.File
def recursiveListFiles(f: File): Array[File] = {
val (dir, files) = f.listFiles.partition(_.isDirectory)
files ++ dir.flatMap(recursiveListFiles)
}
Для этого вы можете использовать хвостовую рекурсию:
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))
}
}
Почему вы используете Java-файл вместо Scala AbstractFile?
В Scala AbstractFile поддержка итераторов позволяет написать более краткую версию решения Джеймса Мура:
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)
}
获取路径下所有文件,剔除文件夹
import java.io.File
import scala.collection.mutable.{ArrayBuffer, ListBuffer}
object pojo2pojo {
def main(args: Array[String]): Unit = {
val file = new File("D:\\tmp\\tmp")
val files = recursiveListFiles(file)
println(files.toList)
// List(D:\tmp\tmp\1.txt, D:\tmp\tmp\a\2.txt)
}
def recursiveListFiles(f: File):ArrayBuffer[File] = {
val all = collection.mutable.ArrayBuffer(f.listFiles:_*)
val files = all.filter(_.isFile)
val dirs = all.filter(_.isDirectory)
files ++ dirs.flatMap(recursiveListFiles)
}
}