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.

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

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)

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


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


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

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

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 =>

}

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

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.


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 ().


3

Co powiesz na

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

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
}

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

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 )

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

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


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]())(_ ++ _)
    }
  }

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

-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)
    }
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.