Bir alt dizindeki tüm dosyaları scala olarak nasıl listeleyebilirim?


92

Bir dizindeki dosyaları yinelemeli olarak listelemenin iyi bir "scala-esque" (sanırım işlevsel demek istiyorum) yolu var mı? Belirli bir kalıbı eşleştirmeye ne dersiniz?

Örneğin ardışık tüm dosyalar eşleşen "a*.foo"içinde c:\temp.


os-lib , bir kalıpla eşleşen dosyaları özyinelemeli olarak listelemek gibi dosya sistemi işlemleri için zarif bir Scala arabirimi sağlar. Tek satırlık basit bir çözüm için cevabımı görün. Scala geliştiricilerinin, sizi gereksiz yere ayrıntılı ve karmaşık kodlar yazmaya zorlayan düşük seviyeli java.io ve java.nio kitaplıklarından dolayı acı çekmeleri gerekmez.
yetkileri

Yanıtlar:


113

Scala kodu, dizinleri okumak dahil G / Ç ile ilgilenmek için tipik olarak Java sınıflarını kullanır. Yani şöyle bir şey yapmalısın:

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

Tüm dosyaları toplayabilir ve ardından bir normal ifade kullanarak filtreleyebilirsiniz:

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

Veya normal ifadeyi yinelemeli aramaya dahil edebilirsiniz:

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
UYARI: Bu kodu çalıştırdım ve bazen f.listFiles null döndürüyor (nedenini bilmiyorum ama Mac'imde çalışıyor) ve recursiveListFiles işlevi çöküyor. Scala'da zarif bir boş denetim oluşturacak kadar deneyimli değilim, ancak bunlar == null benim için çalıştıysa boş bir dizi döndürüyorum.
Ocak

2
@Jan - listFilesdöner nulleğer fbir dizine işaret veya GÇ hatası (en azından göre Java spec) varsa etmez. Boş kontrol eklemek, üretimde kullanım için muhtemelen akıllıca olacaktır.
Rex Kerr

5
Schwarz @Peter - Sen hâlâ mümkün olduğundan, boş çek gerek f.isDirectorygerçek dönmek fakat f.listFilesdönmek için null. Örneğin, dosyaları okuma izniniz yoksa, bir null. Her iki çeke sahip olmaktansa, sadece bir boş çek eklerim.
Rex Kerr

1
Aslında, f.listFilesnull ne zaman döndürdüğü için yalnızca boş kontrolüne ihtiyacınız vardır !f.isDirectory.
Duncan McGregor

2
Boş kontrol ile ilgili olarak, en deyimsel yol, boş değeri seçeneğe dönüştürmek ve haritayı kullanmaktır. Dolayısıyla, atama değeridir: These = Option (f.listFiles) ve ++ operatörü, sonunda bir 'getOrElse' olan bir harita işleminin içinde
Veya Peles

46

Sonsuz dosya sistemi üzerinde yineleme yapabileceğiniz için Akışlar ile çözümü tercih ederim (Akışlar tembel olarak değerlendirilen koleksiyonlardır)

import scala.collection.JavaConversions._

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

Arama örneği

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

4
Alternatif sözdizimi:def getFileTree(f: File): Stream[File] = f #:: Option(f.listFiles()).toStream.flatten.flatMap(getFileTree)
VasiliNovikov

3
Niyetinize katılıyorum ama bu sizin çözümünüz anlamsız. listFiles () zaten tam olarak değerlendirilmiş bir dizi döndürür ve daha sonra bunu "tembel olarak" toStream üzerinde değerlendirirsiniz. Bir akış formu kazı kazanına ihtiyacınız var, java.nio.file.DirectoryStream'i arayın.
Daniel Langdon

7
@Daniel kesinlikle katı değil, dizinleri tembel bir şekilde tekrar ediyor.
Guillaume Massé

3
Bunu şimdi sonsuz dosya sistemimde deneyeceğim :-)
Brian Agnew

Dikkat: JavaConversions artık kullanımdan kaldırılmıştır. JavaConverters ve asScala dekorasyon uygulamasını kullanın.
Suma

25

Java 1.7'den itibaren hepiniz java.nio kullanıyor olmalısınız. Yerel performansa yakın performans sunar (java.io çok yavaştır) ve bazı yararlı yardımcıları vardır

Ancak Java 1.8 tam olarak aradığınız şeyi sunar:

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)

Dosya eşleştirme de istediniz. Deneyin java.nio.file.Files.findve ayrıcajava.nio.file.Files.newDirectoryStream

Buradaki belgelere bakın: http://docs.oracle.com/javase/tutorial/essential/io/walk.html


aldım: Hata: (38, 32) değeri asScala java.util.Iterator [java.nio.file.Path] Files.walk (dir) .iterator (). asScala.filter (Files.isRegularFile ( _)). foreach (println)
stuart


11

Scala, çok paradigmalı bir dildir. Bir dizini yinelemenin iyi bir "scala-esque" yolu, var olan bir kodu yeniden kullanmaktır!

Commons- io'yu bir dizini yinelemenin mükemmel ölçeklendirilmiş bir yolunu kullanmayı düşünürdüm . Bunu kolaylaştırmak için bazı örtük dönüşümler kullanabilirsiniz. Sevmek

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'nın akış çözümünü seviyorum, ancak bu (ve diğerleri) gizli dizinlerde tekrarlanıyor. listFilesDizin olmayan bir için null döndüren olguyu kullanarak da sadeleştirebiliriz .

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

Şimdi dosyaları listeleyebiliriz

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

veya daha sonra işlenmek üzere tüm akışı gerçekleştirin

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

6

Apache Commons Io'nun FileUtils'i bir satıra sığar ve oldukça okunabilirdir:

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ür bilgisi eklemem gerekiyordu: FileUtils.listFiles (yeni Dosya ("c: \ temp"), Array ("foo"), true) .toArray (Dizi [Dosya] ()). Foreach {f =>}
Jason Wheeler

Sağlanan uzantıların büyük / küçük harfe tam olarak uyması gerektiğinden, büyük / küçük harfe duyarlı bir dosya sisteminde pek kullanışlı değildir. ExtensionFileComparator'ı belirtmenin bir yolu yok gibi görünüyor.
Brent Faust

geçici bir çözüm: Array ("foo", "FOO", "png", "PNG") sağlayın
Renaud

5

Henüz kimse bahsetmedi 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'ya bir göz atın

Burada, Directory sınıfında derin listeleme işlevini içeren çok faydalı bazı yardımcı programlar vardır.

Doğru hatırlıyorsam, bu retronym tarafından vurgulanmıştır (muhtemelen katkıda bulunmuştur) ve io standart kitaplıkta yeni ve daha eksiksiz bir uygulama almadan önce bir geçici boşluk olarak görülmüştür.


3

Ve burada @DuncanMcGregor'daki akış çözümünün @ Rick-777 filtresinin bir karışımı:

  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 }

Bu, descendCheck () işlevi ile hangi tür dizinlerin tekrarlanacağına karar vermenize izin verirken (potansiyel olarak çok büyük ve çok yavaş) Liste [Dosya] yerine size bir Akış [Dosya] verir.


3

Ne dersin

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

3

Scala, deneysel olarak kabul edilen ancak işi yapan 'scala.reflect.io' kitaplığına sahiptir.

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

3

@Rex Kerr'in önerdiği çözümün zarafetini ve sadeliğini kişisel olarak seviyorum. Ama işte bir kuyruklu yinelemeli sürüm nasıl görünebilir:

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

taşma ne olacak?
norisknofun

1

İşte Rex Kerr'inkine benzer bir çözüm, ancak bir dosya filtresi içeriyor:

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

Yöntem, Dizi [Dosya] 'dan biraz daha uygun olan bir Liste [Dosya] döndürür. Ayrıca, gizli olan tüm dizinleri de yok sayar (yani "." İle başlayan).

Seçtiğiniz bir dosya filtresi kullanılarak kısmen uygulanır, örneğin:

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

1

Yalnızca Scala içeren en basit çözüm (Scala derleyici kitaplığına ihtiyaç duymanızın sakıncası yoksa):

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

Aksi takdirde, @ Renaud'un çözümü kısa ve tatlıdır (Apache Commons FileUtils'i çekmeyi düşünmüyorsanız):

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

dirJava.io. dosyası nerede :

new File("path/to/dir")

1

Görünüşe göre kimse scala-iokütüphaneden scala-incubrator'dan bahsetmiyor ...

import scalax.file.Path

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

Veya ile implicit

import scalax.file.ImplicitConversions.string2path

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

Ya da implicitaçıkça istiyorsanız ...

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

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

Belgeler burada mevcuttur: http://jesseeichar.github.io/scala-io-doc/0.4.3/index.html#!/file/glob_based_path_sets


0

Bu büyü benim için çalışıyor:

  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

Bunun için kuyruk özyinelemesini kullanabilirsiniz:

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

0

os-lib , Scala'da dosyaları yinelemeli olarak listelemenin en kolay yoludur.

os.walk(os.pwd/"countries").filter(os.isFile(_))

Soruda "a*.foo"belirtilen kalıpla eşleşen tüm dosyaları yinelemeli olarak nasıl listeleyeceğiniz aşağıda açıklanmıştır :

os.walk(os.pwd/"countries").filter(_.segments.toList.last matches "a.*\\.foo")

os-lib, diğer alternatiflerden çok daha zarif ve güçlüdür. osKolayca taşıyabileceğiniz, yeniden adlandırabileceğiniz nesneleri döndürür . Artık hantal Java kitaplıklarıyla uğraşmanıza gerek yok.

Bu kitaplığı yerel makinenizde denemek isterseniz çalıştırabileceğiniz bir kod parçacığı:

os.makeDir(os.pwd/"countries")
os.makeDir(os.pwd/"countries"/"colombia")
os.write(os.pwd/"countries"/"colombia"/"medellin.txt", "q mas pues")
os.write(os.pwd/"countries"/"colombia"/"a_something.foo", "soy un rolo")
os.makeDir(os.pwd/"countries"/"brasil")
os.write(os.pwd/"countries"/"brasil"/"a_whatever.foo", "carnaval")
os.write(os.pwd/"countries"/"brasil"/"a_city.txt", "carnaval")

println(os.walk(os.pwd/"countries").filter(os.isFile(_))) bunu döndürecek:

ArraySeq(
  /.../countries/brasil/a_whatever.foo, 
  /.../countries/brasil/a_city.txt, 
  /.../countries/colombia/a_something.foo, 
  /.../countries/colombia/medellin.txt)

os.walk(os.pwd/"countries").filter(_.segments.toList.last matches "a.*\\.foo") bunu döndürecek:

ArraySeq(
  /.../countries/brasil/a_whatever.foo, 
  /.../countries/colombia/a_something.foo)

Os-lib'in nasıl kullanılacağı hakkında daha fazla ayrıntı için buraya bakın .


-1

Neden Scala'nın AbstractFile'ı yerine Java'nın Dosyasını kullanıyorsunuz?

Scala'nın AbstractFile'ı ile yineleyici desteği, James Moore'un çözümünün daha kısa bir versiyonunun yazılmasına izin verir:

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)
    }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.