ฉันจะแสดงรายการไฟล์ทั้งหมดในไดเร็กทอรีย่อยใน scala ได้อย่างไร


91

มีวิธี "scala-esque" ที่ดี (ฉันเดาว่าฉันหมายถึงฟังก์ชัน) ในการแสดงรายการไฟล์ซ้ำ ๆ ในไดเรกทอรีหรือไม่? แล้วการจับคู่รูปแบบเฉพาะล่ะ?

ตัวอย่างเช่นเรียกซ้ำไฟล์ทั้งหมดที่ตรงกัน"a*.foo"ในc:\temp.

คำตอบ:


112

โดยทั่วไปโค้ด Scala จะใช้คลาส Java สำหรับจัดการกับ I / O รวมถึงการอ่านไดเร็กทอรี ดังนั้นคุณต้องทำสิ่งต่างๆเช่น:

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

คุณสามารถรวบรวมไฟล์ทั้งหมดแล้วกรองโดยใช้ regex:

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

หรือคุณสามารถรวม regex เข้ากับการค้นหาแบบวนซ้ำ:

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 ที่สง่างามในสกาลา แต่ส่งคืนอาร์เรย์ว่างเปล่าหาก == null เหล่านี้ใช้งานได้สำหรับฉัน
ม.ค.

2
@Jan - listFilesส่งคืนnullหากfไม่ชี้ไปที่ไดเร็กทอรีหรือหากมีข้อผิดพลาด IO (อย่างน้อยตามข้อมูลจำเพาะของ Java) การเพิ่มการตรวจสอบค่าว่างอาจเป็นประโยชน์สำหรับการใช้งานจริง
Rex Kerr

5
@ Peter Schwarz - คุณยังคงต้องตรวจสอบเป็นโมฆะเพราะมันเป็นไปได้สำหรับf.isDirectoryที่จะกลับมาจริง แต่การกลับมาf.listFiles nullตัวอย่างเช่นหากคุณไม่มีสิทธิ์อ่านไฟล์คุณจะได้รับไฟล์null. แทนที่จะมีการตรวจสอบทั้งสองครั้งฉันแค่เพิ่มการตรวจสอบว่างหนึ่งรายการ
Rex Kerr

1
ในความเป็นจริงคุณจะต้องตรวจสอบเป็นโมฆะเป็นผลตอบแทนที่เป็นโมฆะเมื่อf.listFiles !f.isDirectory
Duncan McGregor

2
เกี่ยวกับการตรวจสอบ Null วิธีที่เป็นสำนวนที่สุดคือการแปลงค่าว่างเป็นตัวเลือกและใช้แผนที่ ดังนั้นการกำหนดคือ val เหล่านี้ = Option (f.listFiles) และตัวดำเนินการ ++ อยู่ในการดำเนินการแผนที่โดยมี 'getOrElse' ในตอนท้าย
หรือ Peles

46

ฉันต้องการวิธีแก้ปัญหาด้วย 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)
VasiliNovikov

3
ฉันเห็นด้วยกับเจตนาของคุณ แต่วิธีนี้ไม่มีจุดหมาย listFiles () ส่งคืนอาร์เรย์ที่ประเมินโดยสมบูรณ์แล้วซึ่งตอนนั้น "เกียจคร้าน" ของคุณจะประเมินบน toStream คุณต้องการแบบฟอร์มสตรีมเริ่มต้นให้มองหา java.nio.file.DirectoryStream
Daniel Langdon

7
@ แดเนียลไม่เข้มงวดอย่างแน่นอนมันเรียกซ้ำไดเรกทอรีอย่างเฉื่อยชา
Guillaume Massé

3
ฉันจะลองตอนนี้กับระบบไฟล์ที่ไม่มีที่สิ้นสุดของฉัน :-)
Brian Agnew

ระวัง: 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)
สจ๊วร์


11

Scala เป็นภาษาที่มีหลายกระบวนทัศน์ วิธี "scala-esque" ที่ดีในการวนซ้ำไดเร็กทอรีคือการนำโค้ดที่มีอยู่กลับมาใช้ใหม่!

ฉันจะพิจารณาใช้ commons-io ซึ่งเป็นวิธี scala-esque ที่สมบูรณ์แบบในการวนซ้ำไดเร็กทอรี คุณสามารถใช้การแปลงโดยนัยเพื่อให้ง่ายขึ้น ชอบ

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 สำหรับ non-directory

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"), Array ("foo"), true) .toArray (Array [File] ()) foreach {f =>}
Jason Wheeler

ไม่มีประโยชน์มากนักในระบบไฟล์ที่คำนึงถึงตัวพิมพ์เล็กและใหญ่เนื่องจากนามสกุลที่ให้มาต้องตรงกับตัวพิมพ์ทุกประการ ดูเหมือนจะไม่มีวิธีระบุ ExtensionFileComparator
Brent Faust

วิธีแก้ปัญหา: ระบุ Array ("foo", "FOO", "png", "PNG")
Renaud


3

ลองดูที่ scala.tools.nsc.io

มียูทิลิตี้ที่มีประโยชน์บางอย่างรวมถึงฟังก์ชันการลงรายการในคลาส Directory

ถ้าฉันจำไม่ผิดสิ่งนี้ถูกเน้น (อาจมีส่วนร่วม) โดย retronym และถูกมองว่าเป็นจุดหยุดก่อนที่ 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 }

สิ่งนี้จะช่วยให้คุณได้สตรีม [ไฟล์] แทนที่จะเป็นรายการ [ไฟล์] (อาจใหญ่และช้ามาก) ในขณะที่ให้คุณตัดสินใจว่าจะเรียกคืนไดเร็กทอรีประเภทใดด้วยฟังก์ชันลงมาตรวจสอบ ()



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

นี่เป็นวิธีแก้ปัญหาที่คล้ายกันกับ Rex Kerr แต่รวมตัวกรองไฟล์:

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)

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 แทน AbstractFile ของ Scala

ด้วย AbstractFile ของ Scala การสนับสนุนตัววนซ้ำช่วยให้สามารถเขียนโซลูชันของ 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)
    }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.