Làm cách nào để liệt kê tất cả các tệp trong một thư mục con trong scala?


90

Có một cách tốt "scala-esque" (tôi đoán ý tôi là có chức năng) để liệt kê đệ quy các tệp trong một thư mục không? Điều gì về việc kết hợp một mẫu cụ thể?

Ví dụ: đệ quy tất cả các tệp khớp "a*.foo"trong c:\temp.

Câu trả lời:


112

Mã Scala thường sử dụng các lớp Java để xử lý I / O, bao gồm cả việc đọc các thư mục. Vì vậy, bạn phải làm một cái gì đó như:

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

Bạn có thể thu thập tất cả các tệp và sau đó lọc bằng regex:

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

Hoặc bạn có thể kết hợp regex vào tìm kiếm đệ quy:

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
CẢNH BÁO: Tôi đã chạy mã này và đôi khi f.listFiles trả về null (không biết tại sao nhưng trên máy mac của tôi thì có) và chức năng recursiveListFiles bị lỗi. Tôi không đủ kinh nghiệm để tạo một kiểm tra null thanh lịch trong scala, nhưng trả về một mảng trống nếu những == null này phù hợp với tôi.
Ngày

2
@Jan - listFilestrả về nullnếu fkhông trỏ đến một thư mục hoặc nếu có lỗi IO (ít nhất là theo thông số Java). Thêm kiểm tra null có lẽ là khôn ngoan cho việc sử dụng sản xuất.
Rex Kerr

5
@Peter Schwarz - Bạn vẫn cần kiểm tra null, vì có thể f.isDirectorytrả về true nhưng f.listFilesphải trả về null. Ví dụ: nếu bạn không có quyền đọc các tệp, bạn sẽ nhận được null. Thay vì có cả hai séc, tôi chỉ thêm một séc rỗng.
Rex Kerr

1
Trong thực tế, bạn chỉ cần kiểm tra null, vì f.listFilestrả về null khi !f.isDirectory.
Duncan McGregor

2
Về kiểm tra Null, cách dễ hiểu nhất là chuyển đổi null thành tùy chọn và sử dụng bản đồ. Vì vậy, việc chuyển nhượng là val những = Option (f.listFiles) và các nhà điều hành ++ là bên trong một hoạt động bản đồ với 'getOrElse' ở cuối
Hoặc Peles

47

Tôi muốn giải pháp với Luồng vì bạn có thể lặp lại qua hệ thống tệp vô hạn (Luồng là tập hợp được đánh giá lười biếng)

import scala.collection.JavaConversions._

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

Ví dụ để tìm kiếm

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

4
Cú pháp thay thế:def getFileTree(f: File): Stream[File] = f #:: Option(f.listFiles()).toStream.flatten.flatMap(getFileTree)
VasiliNovikov

3
Tôi đồng ý với ý định của bạn, nhưng giải pháp này của bạn là vô nghĩa. listFiles () đã trả về một mảng được đánh giá đầy đủ, mảng mà sau đó bạn "lười biếng" đánh giá trên toStream. Bạn cần một bản ghi đầu dòng, hãy tìm java.nio.file.DirectoryStream.
Daniel Langdon

7
@Daniel nó không hoàn toàn nghiêm ngặt, nó lặp lại các thư mục một cách lười biếng.
Guillaume Massé

3
Tôi sẽ cố gắng mà ngay bây giờ trên hệ thống tập tin vô hạn của tôi :-)
Brian Agnew

Lưu ý: JavaConversions hiện không được dùng nữa. Sử dụng JavaConverters và trang trang trí asScala.
Suma

25

Kể từ Java 1.7, tất cả bạn nên sử dụng java.nio. Nó cung cấp hiệu suất gần như nguyên bản (java.io rất chậm) và có một số trợ giúp hữu ích

Nhưng Java 1.8 giới thiệu chính xác những gì bạn đang tìm kiếm:

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)

Bạn cũng đã yêu cầu đối sánh tệp. Hãy thử java.nio.file.Files.findvà cũngjava.nio.file.Files.newDirectoryStream

Xem tài liệu tại đây: http://docs.oracle.com/javase/tutorial/essential/io/walk.html


tôi nhận được: Lỗi: (38, 32) giá trị asScala không phải là thành viên của java.util.Iterator [java.nio.file.Path] Files.walk (dir) .iterator (). asScala.filter (Files.isRegularFile ( _)). foreach (println)
stuart


11

Scala là một ngôn ngữ đa mô hình. Một cách tốt "scala-esque" để lặp lại một thư mục là sử dụng lại mã hiện có!

Tôi sẽ xem xét việc sử dụng dấu phẩy-io là một cách hoàn hảo để lặp lại một thư mục. Bạn có thể sử dụng một số chuyển đổi ngầm để thực hiện dễ dàng hơn. Giống

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

Tôi thích giải pháp stream của yura, nhưng nó (và những giải pháp khác) tái sinh vào các thư mục ẩn. Chúng ta cũng có thể đơn giản hóa bằng cách sử dụng thực tế là listFilestrả về null cho một thư mục không phải là thư mục.

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

Bây giờ chúng ta có thể liệt kê các tệp

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

hoặc nhận ra toàn bộ luồng để xử lý sau

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

6

Apache Commons Io's FileUtils nằm trên một dòng và khá dễ đọc:

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

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

}

Tôi phải thêm thông tin loại: FileUtils.listFiles (new File ("c: \ temp"), Array ("foo"), true) .toArray (Array [File] ()). Foreach {f =>}
Jason Wheeler

Nó không hữu ích lắm trên hệ thống tệp phân biệt chữ hoa chữ thường vì các phần mở rộng được cung cấp phải khớp chính xác với chữ hoa và chữ thường. Dường như không có cách nào để chỉ định ExtensionFileComparator.
Brent Faust

giải pháp thay thế: cung cấp Mảng ("foo", "FOO", "png", "PNG")
Renaud

5

Chưa ai đề cập 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

Hãy xem scala.tools.nsc.io

Có một số tiện ích rất hữu ích ở đó bao gồm chức năng liệt kê sâu trên lớp Thư mục.

Nếu tôi nhớ không nhầm thì điều này đã được đánh dấu (có thể được đóng góp) bởi từ viết tắt và được coi như một điểm dừng trước khi tôi có được một triển khai mới và hoàn chỉnh hơn trong thư viện tiêu chuẩn.


3

Và đây là hỗn hợp của giải pháp phát trực tiếp từ @DuncanMcGregor với bộ lọc từ @ 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 }

Điều này cung cấp cho bạn một Luồng [Tệp] thay vì một Danh sách [Tệp] (có thể rất lớn và rất chậm) trong khi cho phép bạn quyết định loại thư mục nào sẽ đệ quy vào bằng hàm hậu duệ ().


3

Làm thế nào về

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

3

Scala có thư viện 'scala.reflect.io' được coi là thử nghiệm nhưng không hoạt động

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

3

Cá nhân tôi thích sự tinh tế và đơn giản của giải pháp được đề xuất của @Rex Kerr. Nhưng đây là phiên bản đệ quy đuôi có thể trông như thế nào:

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

còn tràn thì sao?
norisknofun

1

Đây là một giải pháp tương tự cho Rex Kerr's, nhưng tích hợp bộ lọc tệp:

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

Phương thức này trả về một Danh sách [Tệp], tiện lợi hơn một chút so với Mảng [Tệp]. Nó cũng bỏ qua tất cả các thư mục bị ẩn (tức là bắt đầu bằng '.').

Nó được áp dụng một phần bằng cách sử dụng bộ lọc tệp bạn chọn, ví dụ:

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

1

Giải pháp đơn giản nhất chỉ dành cho Scala (nếu bạn không ngại yêu cầu thư viện trình biên dịch Scala):

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

Mặt khác, giải pháp của @ Renaud là ngắn gọn và ngọt ngào (nếu bạn không ngại sử dụng Apache Commons FileUtils):

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

Trong trường hợp dirlà một java.io.File:

new File("path/to/dir")

1

Có vẻ như không ai đề cập đến scala-iothư viện từ vườn ươm scala ...

import scalax.file.Path

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

Hoặc với implicit

import scalax.file.ImplicitConversions.string2path

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

Hoặc nếu bạn muốn implicitrõ ràng ...

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

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

Tài liệu có sẵn tại đây: http://jesseeichar.github.io/scala-io-doc/0.4.3/index.html#!/file/glob_based_path_sets


0

Câu thần chú này phù hợp với tôi:

  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

Bạn có thể sử dụng đệ quy đuôi cho nó:

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

Tại sao bạn lại sử dụng File của Java thay vì AbstractFile của Scala?

Với AbstractFile của Scala, hỗ trợ trình lặp cho phép viết một phiên bản ngắn gọn hơn của giải pháp của James Moore:

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)
    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.