Jak wyświetlić listę wszystkich plików w podkatalogu w scali?

91

Czy istnieje dobry "scala-esque" (chyba mam na myśli funkcjonalny) sposób rekurencyjnego wyświetlania plików w katalogu? A co z dopasowaniem określonego wzorca?

Na przykład rekurencyjnie wszystkie pliki pasujące "a*.foo"do c:\temp.

Nick Fortescue
źródło

Odpowiedzi:

112

Kod Scala zazwyczaj używa klas Java do obsługi operacji we / wy, w tym do czytania katalogów. Musisz więc zrobić coś takiego:

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

Możesz zebrać wszystkie pliki, a następnie przefiltrować za pomocą wyrażenia regularnego:

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

Lub możesz włączyć to wyrażenie regularne do wyszukiwania rekurencyjnego:

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))
}
Rex Kerr
źródło
7
OSTRZEŻENIE: uruchomiłem ten kod i czasami f.listFiles zwraca wartość null (nie wiem dlaczego, ale na moim Macu tak) i funkcja recursiveListFiles ulega awarii. Nie mam wystarczającego doświadczenia, aby zbudować elegancki null check w scali, ale zwracam pustą tablicę, jeśli te == null zadziałały.
Jan
2
@Jan - listFileszwraca, nulljeśli fnie wskazuje na katalog lub jeśli wystąpił błąd we / wy (przynajmniej zgodnie ze specyfikacją Java). Dodanie czeku zerowego jest prawdopodobnie rozsądne do użytku produkcyjnego.
Rex Kerr
5
@Peter Schwarz - Nadal potrzebujesz sprawdzenia zerowego, ponieważ możliwe jest f.isDirectoryzwrócenie prawdy, ale f.listFileszwrócenie null. Na przykład, jeśli nie masz uprawnień do odczytu plików, otrzymasz rozszerzenie null. Zamiast mieć oba sprawdzenia, po prostu dodam jeden czek zerowy.
Rex Kerr,
1
W rzeczywistości potrzebujesz tylko sprawdzenia null, ponieważ f.listFileszwraca null when !f.isDirectory.
Duncan McGregor
2
Jeśli chodzi o sprawdzenie wartości Null, najbardziej idiomatycznym sposobem byłoby przekonwertowanie wartości null na opcję i użycie mapy. Więc przypisanie jest val te = Option (f.listFiles), a operator ++ znajduje się wewnątrz operacji na mapie z 'getOrElse' na końcu
lub Peles
46

Wolałbym rozwiązanie ze strumieniami, ponieważ można iterować w nieskończonym systemie plików (strumienie są leniwie ocenianymi kolekcjami)

import scala.collection.JavaConversions._

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

Przykład wyszukiwania

getFileTree(new File("c:\\main_dir")).filter(_.getName.endsWith(".scala")).foreach(println)
yura
źródło
4
Alternatywna składnia:def getFileTree(f: File): Stream[File] = f #:: Option(f.listFiles()).toStream.flatten.flatMap(getFileTree)
VasiliNovikov
3
Zgadzam się z twoim zamiarem, ale to twoje rozwiązanie jest bezcelowe. listFiles () już zwraca w pełni obliczoną tablicę, którą następnie "leniwie" oceniasz na toStream. Potrzebujesz scratchu formularza strumienia, poszukaj java.nio.file.DirectoryStream.
Daniel Langdon,
7
@Daniel nie jest to absolutnie surowe, leniwie powraca do katalogów.
Guillaume Massé
3
Spróbuję tego teraz na moim nieskończonym systemie plików :-)
Brian Agnew
Uwaga: JavaConversions jest teraz przestarzała. Używaj dekoracji JavaConverters i asScala podczas odczytu.
Suma
25

Od wersji Java 1.7 wszyscy powinniście używać java.nio. Oferuje wydajność zbliżoną do natywnej (java.io działa bardzo wolno) i ma kilka przydatnych pomocników

Ale Java 1.8 wprowadza dokładnie to, czego szukasz:

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)

Poprosiłeś również o dopasowanie pliku. Spróbuj java.nio.file.Files.findi teżjava.nio.file.Files.newDirectoryStream

Zobacz dokumentację tutaj: http://docs.oracle.com/javase/tutorial/essential/io/walk.html

monzonj
źródło
otrzymuję: Błąd: (38, 32) wartość asScala nie jest członkiem java.util.Iterator [java.nio.file.Path] Files.walk (dir) .iterator (). asScala.filter (Files.isRegularFile ( _)). foreach (println)
stuart
20
for (file <- new File("c:\\").listFiles) { processFile(file) }

http://langref.org/scala+java/files

Phil
źródło
17
To robi tylko jeden poziom; nie wraca do katalogów w c: \.
James Moore
11

Scala jest językiem wieloparadygmatycznym. Dobrym sposobem iteracji katalogu w stylu „scala” byłoby ponowne użycie istniejącego kodu!

Rozważałbym użycie commons-io jako idealnie skalowalnego sposobu iteracji katalogu. Możesz użyć niektórych niejawnych konwersji, aby to ułatwić. Lubić

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))
}
ArtemGr
źródło
11

Podoba mi się rozwiązanie strumieniowe Yury, ale (i inne) powraca do ukrytych katalogów. Możemy również uprościć, wykorzystując fakt, że listFileszwraca null dla niekatalogu.

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

Teraz możemy wyświetlić listę plików

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

lub zrealizuj cały strumień do późniejszego przetworzenia

tree(new File("dir"), true).toArray
Duncan McGregor
źródło
6

FileUtils Apache Commons Io mieści się w jednej linii i jest całkiem czytelny:

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

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

}
Renaud
źródło
Musiałem dodać informacje o typie: FileUtils.listFiles (new File ("c: \ temp"), Array ("foo"), true) .toArray (Array [File] ()). Foreach {f =>}
Jason Wheeler
Nie jest to zbyt przydatne w systemie plików uwzględniającym wielkość liter, ponieważ dostarczone rozszerzenia muszą dokładnie pasować do wielkości liter. Wydaje się, że nie ma sposobu na określenie ExtensionFileComparator.
Brent Faust
obejście: podaj Array („foo”, „FOO”, „png”, „PNG”)
Renaud
5

Nikt jeszcze nie wspomniał o 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")) 
Phil
źródło
3

Spójrz na scala.tools.nsc.io

Jest tam kilka bardzo przydatnych narzędzi, w tym funkcje głębokiego wyświetlania list w klasie Directory.

Jeśli dobrze pamiętam, zostało to podkreślone (prawdopodobnie wniesione) przez retronimę i było postrzegane jako przerwa, zanim io otrzyma świeżą i pełniejszą implementację w standardowej bibliotece.

Don Mackenzie
źródło
3

A oto mieszanina roztworu strumienia z @DuncanMcGregor z filtrem z @ 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 }

Daje to Stream [File] zamiast (potencjalnie ogromnej i bardzo powolnej) List [File], pozwalając jednocześnie zdecydować, które rodzaje katalogów mają się powtarzać za pomocą funkcji descendCheck ().

James Moore
źródło
3

Co powiesz na

   def allFiles(path:File):List[File]=
   {    
       val parts=path.listFiles.toList.partition(_.isDirectory)
       parts._2 ::: parts._1.flatMap(allFiles)         
   }
Dino Fancellu
źródło
3

Scala ma bibliotekę „scala.reflect.io”, która uważana jest za eksperymentalną, ale wykonuje swoją pracę

import scala.reflect.io.Path
Path(path) walkFilter { p => 
  p.isDirectory || """a*.foo""".r.findFirstIn(p.name).isDefined
}
roterl
źródło
3

Osobiście podoba mi się elegancja i prostota rozwiązania proponowanego przez @Rex Kerr. Ale oto jak może wyglądać rekurencyjna wersja ogona:

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)
}
polbotinka
źródło
a co z przepełnieniem?
norisknofun
1

Oto rozwiązanie podobne do Rexa Kerra, ale zawierające filtr plików:

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

Metoda zwraca List [File], co jest nieco wygodniejsze niż Array [File]. Ignoruje również wszystkie katalogi, które są ukryte (tj. Zaczynające się od „.”).

Jest częściowo stosowany przy użyciu wybranego filtru plików, na przykład:

val srcDir = new File( ... )
val htmlFiles = findFiles( _.getName endsWith ".html" )( srcDir )
Rick-777
źródło
1

Najprostsze rozwiązanie tylko dla Scala (jeśli nie masz nic przeciwko wymaganiu biblioteki kompilatora Scala):

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

W przeciwnym razie rozwiązanie @ Renaud jest krótkie i słodkie (jeśli nie masz nic przeciwko ściągnięciu Apache Commons FileUtils):

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

Gdzie dirjest plik java.io.:

new File("path/to/dir")
Brent Faust
źródło
1

Wygląda na to, że nikt nie wspomina o scala-iobibliotece z inkubatora scala ...

import scalax.file.Path

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

Lub z implicit

import scalax.file.ImplicitConversions.string2path

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

Lub jeśli chcesz implicitwyraźnie ...

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

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

Dokumentacja jest dostępna tutaj: http://jesseeichar.github.io/scala-io-doc/0.4.3/index.html#!/file/glob_based_path_sets

remis
źródło
0

To zaklęcie działa dla mnie:

  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]())(_ ++ _)
    }
  }
Connor Doyle
źródło
0

Możesz użyć do tego rekurencji ogona:

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))
  }
}
Milind
źródło
-1

Dlaczego używasz pliku Java zamiast AbstractFile Scali?

Dzięki AbstractFile Scali obsługa iteratorów umożliwia napisanie bardziej zwięzłej wersji rozwiązania Jamesa Moore'a:

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)
    }
Nicolas Rouquette
źródło