Як перерахувати всі файли в підкаталозі в Scala?


90

Чи є хороший "масштабований" (мабуть, я маю на увазі функціональний) спосіб рекурсивного перерахування файлів у каталозі? А як щодо узгодження певного зразка?

Наприклад рекурсивно всі файли , відповідні "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 це робить), і рекурсивнаListFiles виходить з ладу. Я недостатньо досвідчений, щоб створити елегантну перевірку 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.
Duncan McGregor

2
Щодо перевірки Null, найбільш ідіоматичним способом було б перетворити null на опцію та використовувати карту. Отже, присвоєнням є val these = Option (f.listFiles), а оператор ++ знаходиться всередині операції з картою з «getOrElse» в кінці
Або Пелеш

47

Я віддав би перевагу рішенню з потоками, тому що ви можете перебирати нескінченну файлову систему (потоки - це ліниві оцінювані колекції)

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 та asScala для декорування в потоці.
Сума

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


11

Scala - це багатопарадигмна мова. Хорошим "масштабованим" способом ітерації каталогу буде повторне використання існуючого коду!

Я б вважав використання 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))
}

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

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 (новий файл ("c: \ temp"), масив ("foo"), істина) .toArray (масив [файл] ()). Foreach {f =>}
Джейсон Wheeler

Це не дуже корисно для чутливої ​​до регістру файлової системи, оскільки надані розширення повинні точно відповідати регістру. Здається, немає способу вказати ExtensionFileComparator.
Брент Фауст,

обхідний шлях: надати масив ("foo", "FOO", "png", "PNG")
Рено

5

Ще ніхто не згадував 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

Погляньте на 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 }

Це дає вам Потік [Файл] замість (потенційно величезного і дуже повільного) Списку [Файл], дозволяючи Вам вирішувати, до яких типів каталогів слід повертатися за допомогою функції descendCheck ().


3

Як щодо

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

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 [Файл], який є дещо зручнішим, ніж Array [Файл]. Він також ігнорує всі приховані каталоги (тобто, починаючи з '.').

Це частково застосовується за допомогою вибраного вами файлового фільтра, наприклад:

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)

Де dirjava.io.File:

new File("path/to/dir")

1

Здається, ніхто не згадує 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#!/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 замість абстрактного файлу Scala?

З абстрактним файлом Scala, підтримка ітератора дозволяє написати більш стислу версію рішення Джеймса Мура:

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.