Как мне вывести список всех файлов в подкаталоге в scala?


91

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

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

Ответы:


112

Код 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))
}

7
ПРЕДУПРЕЖДЕНИЕ: я запускал этот код, и иногда f.listFiles возвращает null (не знаю почему, но на моем Mac это так), и функция recursiveListFiles аварийно завершает работу. У меня недостаточно опыта, чтобы создать элегантную проверку на null в scala, но я возвращаю пустой массив, если эти == null сработали для меня.
янв., В

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

5
@Peter Schwarz - Вам все еще нужна нулевая проверка, поскольку можно f.isDirectoryвернуть true, но f.listFilesможно вернуть null. Например, если у вас нет разрешения на чтение файлов, вы получите расширение null. Вместо того, чтобы иметь обе проверки, я бы просто добавил одну нулевую проверку.
Рекс Керр,

1
На самом деле вам нужна только проверка на null, так как f.listFilesвозвращает null, когда !f.isDirectory.
Дункан МакГрегор

2
Что касается проверки Null, наиболее идиоматическим способом было бы преобразовать null в option и использовать map. Таким образом, присваивается значение val these = Option (f.listFiles), а оператор ++ находится внутри операции карты с 'getOrElse' в конце
Или Пелеш

46

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

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

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

7
@Daniel это не совсем строго, лениво рекурсирует каталоги.
Гийом Массе

3
Я попробую это прямо сейчас на моей бесконечной файловой системе :-)
Брайан Агнью

Осторожно: JavaConversions устарела. Используйте JavaConverters и instread для оформления asScala.
Suma

25

Начиная с 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


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


11

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

Я бы подумал, что использование commons-io - это идеальный способ итерации каталога в стиле Scala. Чтобы упростить задачу, вы можете использовать некоторые неявные преобразования. подобно

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

Мне нравится решение yura stream, но оно (и другие) рекурсивно перемещается в скрытые каталоги. Мы также можем упростить, используя тот факт, что 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

6

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

}

Мне пришлось добавить информацию о типе: FileUtils.listFiles (new File ("c: \ temp"), Array ("foo"), true) .toArray (Array [File] ()). Foreach {f =>}
Джейсон Wheeler

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

обходной путь: предоставить Array ("foo", "FOO", "png", "PNG")
Renaud


3

Взгляните на scala.tools.nsc.io

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

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


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 }

Это дает вам Stream [File] вместо (потенциально огромного и очень медленного) List [File], в то же время позволяя вам решить, какие типы каталогов рекурсивно переходить с помощью функции DescendCheck ().



3

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

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

1

Вот решение, похожее на решение Рекса Керра, но с фильтром файлов:

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 )

1

Самое простое решение, предназначенное только для 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")

1

scala-ioБиблиотеку из scala-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


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

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

-1

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

В 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)
    }
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.