Bagaimana cara membuat daftar semua file di subdirektori di scala?


91

Apakah ada cara "scala-esque" (maksud saya fungsional) yang baik untuk mendaftar file secara rekursif dalam direktori? Bagaimana dengan mencocokkan pola tertentu?

Misalnya secara rekursif semua file yang cocok "a*.foo"dengan c:\temp.

Jawaban:


112

Kode scala biasanya menggunakan kelas Java untuk menangani I / O, termasuk membaca direktori. Jadi, Anda harus melakukan sesuatu seperti:

import java.io.File
def recursiveListFiles(f: File): Array[File] = {
  val these = f.listFiles
  these ++ these.filter(_.isDirectory).flatMap(recursiveListFiles)
}

Anda dapat mengumpulkan semua file dan kemudian memfilter menggunakan regex:

myBigFileArray.filter(f => """.*\.html$""".r.findFirstIn(f.getName).isDefined)

Atau Anda bisa memasukkan regex ke dalam pencarian rekursif:

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
PERINGATAN: Saya menjalankan kode ini dan kadang-kadang f.listFiles mengembalikan null (tidak tahu mengapa tetapi di Mac saya melakukannya) dan fungsi recursiveListFiles lumpuh. Saya tidak cukup berpengalaman untuk membangun check in scala null yang elegan, tetapi mengembalikan array kosong jika ini == null berfungsi untuk saya.
Jan

2
@ Jan - listFilesmengembalikan nulljika ftidak mengarah ke direktori atau jika ada kesalahan IO (setidaknya sesuai dengan spesifikasi Java). Menambahkan cek nol mungkin bijaksana untuk penggunaan produksi.
Rex Kerr

5
@ Peter Schwarz - Anda masih memerlukan pemeriksaan null, karena bisa saja f.isDirectorymengembalikan true tetapi f.listFilesmengembalikan null. Misalnya, jika Anda tidak memiliki izin untuk membaca file, Anda akan mendapatkan file null. Daripada memiliki kedua cek, saya hanya menambahkan satu cek nol.
Rex Kerr

1
Sebenarnya Anda hanya perlu pemeriksaan null, karena f.listFilesmengembalikan null saat !f.isDirectory.
Duncan McGregor

2
Mengenai pemeriksaan Null, cara yang paling idiomatis adalah mengonversi null menjadi opsi dan menggunakan peta. Jadi tugasnya adalah val ini = Option (f.listFiles) dan operator ++ ada di dalam operasi peta dengan 'getOrElse' di akhir
Atau Peles

46

Saya lebih suka solusi dengan Streams karena Anda dapat melakukan iterasi pada sistem file tak terbatas (Stream adalah koleksi yang dievaluasi malas)

import scala.collection.JavaConversions._

def getFileTree(f: File): Stream[File] =
        f #:: (if (f.isDirectory) f.listFiles().toStream.flatMap(getFileTree) 
               else Stream.empty)

Contoh pencarian

getFileTree(new File("c:\\main_dir")).filter(_.getName.endsWith(".scala")).foreach(println)

4
Sintaks alternatif:def getFileTree(f: File): Stream[File] = f #:: Option(f.listFiles()).toStream.flatten.flatMap(getFileTree)
VasiliNovikov

3
Saya setuju dengan niat Anda, tetapi ini solusi Anda tidak ada gunanya. listFiles () sudah mengembalikan larik yang dievaluasi sepenuhnya, yang kemudian Anda evaluasi "dengan malas" di toStream. Anda memerlukan awal formulir aliran, cari java.nio.file.DirectoryStream.
Daniel Langdon

7
@Daniel tidak sepenuhnya ketat, ini berulang direktori dengan malas.
Guillaume Massé

3
Saya akan mencobanya sekarang di sistem file tak terbatas saya :-)
Brian Agnew

Hati-hati: JavaConversions sekarang tidak digunakan lagi. Gunakan JavaConverters dan instread dekorasi asScala.
Suma

25

Mulai Java 1.7, Anda semua harus menggunakan java.nio. Ia menawarkan kinerja yang mendekati asli (java.io sangat lambat) dan memiliki beberapa pembantu yang berguna

Tapi Java 1.8 memperkenalkan dengan tepat apa yang Anda cari:

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)

Anda juga meminta pencocokan file. Mencobajava.nio.file.Files.find dan jugajava.nio.file.Files.newDirectoryStream

Lihat dokumentasi di sini: http://docs.oracle.com/javase/tutorial/essential/io/walk.html


saya mendapatkan: Kesalahan: (38, 32) nilai asScala bukan anggota java.util.Iterator [java.nio.file.Path] Files.walk (dir) .iterator (). asScala.filter (Files.isRegularFile ( . _)) foreach (println)
stuart


11

Scala adalah bahasa multi-paradigma. Cara "scala-esque" yang baik untuk mengiterasi direktori adalah dengan menggunakan kembali kode yang ada!

Saya akan mempertimbangkan untuk menggunakan commons-io sebagai cara yang sangat scala-esque untuk mengulang direktori. Anda dapat menggunakan beberapa konversi implisit untuk membuatnya lebih mudah. Suka

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

Saya suka solusi streaming yura, tetapi (dan yang lainnya) muncul kembali ke direktori tersembunyi. Kita juga dapat menyederhanakan dengan memanfaatkan fakta yang listFilesmengembalikan null untuk non-direktori.

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

Sekarang kita dapat membuat daftar file

tree(new File(".")).filter(f => f.isFile && f.getName.endsWith(".html")).foreach(println)

atau mewujudkan seluruh aliran untuk diproses nanti

tree(new File("dir"), true).toArray

6

FileUtils Apache Commons Io cocok dalam satu baris, dan cukup mudah dibaca:

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

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

}

Saya harus menambahkan informasi jenis: FileUtils.listFiles (File baru ("c: \ temp"), Array ("foo"), true) .toArray (Array [File] ()). Foreach {f =>}
Jason Wheeler

Ini tidak terlalu berguna pada sistem file yang peka huruf besar kecil karena ekstensi yang disediakan harus sama persis dengan huruf besar / kecil. Tampaknya tidak ada cara untuk menentukan ExtensionFileComparator.
Brent Faust

solusi: sediakan Array ("foo", "FOO", "png", "PNG")
Renaud

5

Belum ada yang menyebutkan 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

Kunjungi scala.tools.nsc.io

Ada beberapa utilitas yang sangat berguna di sana termasuk fungsionalitas daftar mendalam di kelas Direktori.

Jika saya ingat dengan benar, ini disorot (mungkin dikontribusikan) oleh retronim dan dilihat sebagai sementara sebelum io mendapatkan implementasi yang segar dan lebih lengkap di perpustakaan standar.


3

Dan inilah campuran solusi streaming dari @DuncanMcGregor dengan filter dari @ 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 }

Ini memberi Anda Stream [File], bukan Daftar [File] (berpotensi besar dan sangat lambat) sambil membiarkan Anda memutuskan jenis direktori mana yang akan digunakan kembali dengan fungsi descendCheck ().


3

Bagaimana tentang

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

3

Scala memiliki perpustakaan 'scala.reflect.io' yang dianggap eksperimental tetapi berfungsi

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

3

Saya pribadi menyukai keanggunan dan kesederhanaan solusi yang diusulkan @Rex Kerr. Tapi inilah versi rekursif ekornya:

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

bagaimana dengan overflow?
norisknofun

1

Berikut solusi yang mirip dengan Rex Kerr, tetapi menggabungkan filter file:

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

Metode ini mengembalikan Daftar [File], yang sedikit lebih nyaman daripada Array [File]. Ini juga mengabaikan semua direktori yang tersembunyi (yaitu, dimulai dengan '.').

Ini diterapkan sebagian menggunakan filter file pilihan Anda, misalnya:

val srcDir = new File( ... )
val htmlFiles = findFiles( _.getName endsWith ".html" )( srcDir )

1

Solusi paling sederhana Scala-only (jika Anda tidak keberatan memerlukan pustaka kompilator Scala):

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

Jika tidak, solusi @ Renaud singkat dan manis (jika Anda tidak keberatan menggunakan FileUtils Apache Commons):

import scala.collection.JavaConversions._  // enables foreach
import org.apache.commons.io.FileUtils
FileUtils.listFiles(dir, null, true).foreach(println)

Di mana dirfile java.io.:

new File("path/to/dir")

1

Sepertinya tidak ada yang menyebutkan scala-ioperpustakaan dari scala-inkubrator ...

import scalax.file.Path

Path.fromString("c:\temp") ** "a*.foo"

Atau dengan implicit

import scalax.file.ImplicitConversions.string2path

"c:\temp" ** "a*.foo"

Atau jika Anda ingin implicitsecara eksplisit ...

import scalax.file.Path
import scalax.file.ImplicitConversions.string2path

val dir: Path = "c:\temp"
dir ** "a*.foo"

Dokumentasi tersedia di sini: http://jesseeichar.github.io/scala-io-doc/0.4.3/index.html#!/file/glob_based_path_sets


0

Mantra ini bekerja untuk saya:

  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

Anda dapat menggunakan rekursi ekor untuk itu:

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

Mengapa Anda menggunakan File Java sebagai ganti AbstractFile dari Scala?

Dengan AbstractFile Scala, dukungan iterator memungkinkan penulisan versi solusi James Moore yang lebih ringkas:

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)
    }
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.