2010-04-14 3 views
82

Есть ли хороший «scala-esque» (я предполагаю, что я имею в виду функциональный) способ рекурсивно перечислять файлы в каталоге? Как насчет соответствия определенного шаблона?Как я могу перечислить все файлы в подкаталоге в scala?

Например, рекурсивно все файлы, соответствующие "a*.foo" в c:\temp.

ответ

98

В коде Scala обычно используются классы Java для работы с I/O, включая считывающие каталоги. Так что вы должны сделать что-то вроде:

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

ВНИМАНИЕ: Я побежал этот код, а иногда и f.listFiles возвращает значение null (не знаю, почему, но на моем mac оно делает) и recursiveListFiles функция cr пепел. Я недостаточно опытен, чтобы построить элегантную нулевую проверку в scala, но возвращая пустой массив, если эти == null работали для меня. – Jan

+2

@Jan - 'listFiles' возвращает' null', если 'f' не указывает на каталог или если есть ошибка ввода-вывода (по крайней мере, согласно спецификации Java). Добавление нулевой проверки, вероятно, разумно для использования в производстве. –

+0

@Rex Возможно, лучше, чем проверка «null», будет иметь проверку на работоспособность, что 'f' является каталогом в начале функции. Это поможет с удобочитаемостью, поскольку смысл проверки будет очень ясным. Например: 'if (! F.isDirectory) return Array()' –

3

Посмотрите на scala.tools.nsc.io

Есть некоторые очень полезные утилиты там включая глубокую функциональность листинга на классе Directory.

Если я правильно помню, это было подчеркнуто (возможно внесено) ретроном и было замечено как остановка, прежде чем io получит новую и более полную реализацию в стандартной библиотеке.

10

Scala - это язык с несколькими парадигмами. Хорошим «scala-esque» способом итерации каталога было бы повторное использование существующего кода!

Я бы рассмотрел using 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)) 
} 
39

Я предпочел бы решение с Streams becouse вы можете перебрать бесконечную файловую систему (Streams ленивы оцениваемые коллекции)

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) 
+4

Альтернативный синтаксис: 'def getFileTree (f: File): Stream [File] = f # :: Option (f.listFiles()). ToStream.flatten.flatMap (getFileTree)' – VasyaNovikov

+3

Я согласен с вашими намерениями, но это ваш решение бессмысленно. listFiles() уже возвращает полностью оцененный массив, который ваш «лениво» оценивает на toStream. Вам нужна форма потока с нуля, найдите java.nio.file.DirectoryStream. –

+7

@ Даниэль это не совсем строго, он лениво рекурсирует каталоги. –

11

Мне нравится решение потока yura, но оно (и другие) переписывается в скрытые каталоги. Мы также можем упростить, используя тот факт, что listFiles возвращает null для некаталога.

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 
1

Вот аналогичное решение Rex Керра, но включающие фильтр файла:

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

А вот смесь потока раствора из @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 } 

Это дает вам поток [Файл] вместо (потенциально огромного и очень медленного) списка [Файл], позволяя вам решить, какие типы каталогов переписываются с функцией descendCheck().

0

Это колдовство работает для меня:

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

Apache Commons Ио FileUtils помещается на одной строке, и вполне читаема:

import scala.collection.JavaConversions._ // important for 'foreach' 
import org.apache.commons.io.FileUtils 

FileUtils.listFiles(new File("c:\temp"), Array("foo"), true).foreach{ f => 

} 
+0

Мне пришлось добавить информацию о типе: FileUtils.listFiles (новый файл («c: \ temp»), Array («foo»), true). ToArray (Array [File]()). Foreach {f => } –

+0

Это не очень полезно для файловой системы, чувствительной к регистру, поскольку прилагаемые расширения должны точно совпадать. По-видимому, не существует способа указать ExtensionFileComparator. –

+0

Обходной путь: предоставить массив («foo», «FOO», «png», «PNG») – Renaud

3

Как насчет

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

Почему ты используя файл Java вместо Scala's AbstractFile?

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

Scala есть библиотека «scala.reflect.io», который считается экспериментальным, но делает работу

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

Мне лично нравится элегантность и простота предлагаемого решения @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) 
} 
+0

как насчет переполнения? – norisknofun

1

Простейшим Scala-единственное решение (если вы не возражаете, требуя библиотеки компилятора Scala):

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

В противном случае, @ Рейно решение является коротким и сладким (если вы не возражаете, втягивая 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") 
1

Это не похоже, никто упоминает scala-io библиотеки из-incubrator лестницы ...

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#!/file/glob_based_path_sets

9

Начиная с версии Java 1.7 вы должны использовать java.nio. Он предлагает близкую к родной производительность (java.Ио очень медленно) и имеет несколько полезных помощников

Но 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

+0

Я получаю: Ошибка: (38, 32) Значение asScala не является членом java.util.Iterator [java.nio.file.Path] Files.walk (dir) .iterator(). AsScala.filter (Files.isRegularFile (_)). Foreach (println) – stuart

+0

Downvoted, потому что это не компилируется. – DanGordon

+0

Прошу прощения. Я считал, что вы знаете, что «asScala» требует, чтобы вы импортировали «scala.collection.JavaConverters._'. Вы не должны кодировать, если вы не знаете, как google .... – monzonj

3

Никто не упомянул еще 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")) 
0

Вы можете использовать хвостовую рекурсию для него:

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)) 
    } 
} 
Смежные вопросы