Comment lister tous les fichiers d'un sous-répertoire dans scala?

90

Existe-t-il une bonne manière "scala-esque" (je suppose que je veux dire fonctionnelle) de lister de manière récursive des fichiers dans un répertoire? Qu'en est-il de la correspondance avec un modèle particulier?

Par exemple, de manière récursive, tous les fichiers correspondant "a*.foo"au format c:\temp.

Nick Fortescue
la source

Réponses:

112

Le code Scala utilise généralement des classes Java pour gérer les E / S, y compris la lecture des répertoires. Vous devez donc faire quelque chose comme:

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

Vous pouvez collecter tous les fichiers, puis filtrer à l'aide d'une expression régulière:

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

Ou vous pouvez incorporer l'expression régulière dans la recherche récursive:

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
la source
7
AVERTISSEMENT: j'ai exécuté ce code et parfois f.listFiles renvoie null (je ne sais pas pourquoi mais sur mon mac c'est le cas) et la fonction recursiveListFiles se bloque. Je ne suis pas assez expérimenté pour créer un élégant check null dans scala, mais renvoyer un tableau vide si ces == null fonctionnaient pour moi.
janvier
2
@Jan - listFilesrenvoie nullsi fne pointe pas vers un répertoire ou s'il y a une erreur IO (au moins selon la spécification Java). L'ajout d'une vérification nulle est probablement judicieux pour une utilisation en production.
Rex Kerr
5
@Peter Schwarz - Vous avez toujours besoin de la vérification de null, car il est possible f.isDirectoryde retourner vrai mais f.listFilesde retourner null. Par exemple, si vous n'êtes pas autorisé à lire les fichiers, vous obtiendrez un null. Plutôt que d'avoir les deux chèques, j'ajouterais simplement le chèque nul.
Rex Kerr
1
En fait, vous n'avez besoin que de la vérification null, car f.listFilesrenvoie null quand !f.isDirectory.
Duncan McGregor
2
En ce qui concerne la vérification Null, la manière la plus idiomatique serait de convertir l'option null en option et d'utiliser la carte. Donc, l'affectation est val ces = Option (f.listFiles) et l'opérateur ++ est à l'intérieur d'une opération de carte avec un 'getOrElse' à la fin
Ou Peles
47

Je préférerais une solution avec Streams car vous pouvez itérer sur un système de fichiers infini (les Streams sont des collections évaluées paresseusement)

import scala.collection.JavaConversions._

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

Exemple de recherche

getFileTree(new File("c:\\main_dir")).filter(_.getName.endsWith(".scala")).foreach(println)
Yura
la source
4
Syntaxe alternative:def getFileTree(f: File): Stream[File] = f #:: Option(f.listFiles()).toStream.flatten.flatMap(getFileTree)
VasiliNovikov
3
Je suis d'accord avec votre intention, mais cette solution est inutile. listFiles () renvoie déjà un tableau entièrement évalué, que vous évaluez ensuite "paresseusement" sur toStream. Vous avez besoin d'un scratch sous forme de flux, recherchez java.nio.file.DirectoryStream.
Daniel Langdon
7
@Daniel ce n'est pas absolument strict, il récurent les répertoires paresseusement.
Guillaume Massé
3
Je vais essayer cela maintenant sur mon système de fichiers infini :-)
Brian Agnew
Attention: JavaConversions est désormais obsolète. Utilisez JavaConverters et asScala decoration instread.
Suma
25

Depuis Java 1.7, vous devriez tous utiliser java.nio. Il offre des performances proches du natif (java.io est très lent) et a quelques aides utiles

Mais Java 1.8 présente exactement ce que vous recherchez:

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)

Vous avez également demandé une correspondance de fichiers. Essayez java.nio.file.Files.findet aussijava.nio.file.Files.newDirectoryStream

Voir la documentation ici: http://docs.oracle.com/javase/tutorial/essential/io/walk.html

monzonj
la source
j'obtiens: Erreur: (38, 32) valeur asScala n'est pas membre de 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
la source
17
Cela ne fait qu'un seul niveau; il ne rentre pas dans les répertoires de c: \.
James Moore
11

Scala est un langage multi-paradigme. Une bonne manière "scala-esque" d'itérer un répertoire serait de réutiliser un code existant!

J'envisagerais d' utiliser commons-io une manière parfaitement scala-esque d'itérer un répertoire. Vous pouvez utiliser certaines conversions implicites pour faciliter les choses. Comme

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
la source
11

J'aime la solution de flux de yura, mais elle (et les autres) récurent dans des répertoires cachés. Nous pouvons également simplifier en utilisant le fait que listFilesrenvoie null pour un non-répertoire.

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

Maintenant, nous pouvons lister les fichiers

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

ou réalisez le flux entier pour un traitement ultérieur

tree(new File("dir"), true).toArray
Duncan McGregor
la source
6

FileUtils d' Apache Commons Io tient sur une seule ligne et est assez lisible:

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
la source
J'ai dû ajouter des informations de type: FileUtils.listFiles (nouveau fichier ("c: \ temp"), Array ("foo"), true) .toArray (Array [File] ()). Foreach {f =>}
Jason Wheeler
Ce n'est pas très utile sur un système de fichiers sensible à la casse car les extensions fournies doivent correspondre exactement à la casse. Il ne semble pas y avoir de moyen de spécifier ExtensionFileComparator.
Brent Faust
une solution de contournement: fournir Array ("foo", "FOO", "png", "PNG")
Renaud
5

Personne n'a encore mentionné 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
la source
3

Jetez un œil à scala.tools.nsc.io

Il existe des utilitaires très utiles, y compris une fonctionnalité de liste détaillée dans la classe Directory.

Si je me souviens bien, cela a été mis en évidence (peut-être contribué) par retronym et a été considéré comme un palliatif avant que io n'obtienne une implémentation fraîche et plus complète dans la bibliothèque standard.

Don Mackenzie
la source
3

Et voici un mélange de la solution de flux de @DuncanMcGregor avec le filtre de @ 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 }

Cela vous donne un Stream [File] au lieu d'un List [File] (potentiellement énorme et très lent) tout en vous laissant décider dans quels types de répertoires la fonction descendCheck () doit être récurée.

James Moore
la source
3

Que diriez-vous

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

Scala a la bibliothèque 'scala.reflect.io' qui est considérée comme expérimentale mais fait le travail

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

J'aime personnellement l'élégance et la simplicité de la solution proposée par @Rex Kerr. Mais voici à quoi pourrait ressembler une version récursive de queue:

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
la source
qu'en est-il du débordement?
norisknofun
1

Voici une solution similaire à celle de Rex Kerr, mais intégrant un filtre de fichiers:

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

La méthode renvoie un List [File], ce qui est légèrement plus pratique que Array [File]. Il ignore également tous les répertoires masqués (c'est-à-dire commençant par «.»).

Il est partiellement appliqué à l'aide d'un filtre de fichier de votre choix, par exemple:

val srcDir = new File( ... )
val htmlFiles = findFiles( _.getName endsWith ".html" )( srcDir )
Rick-777
la source
1

La solution la plus simple pour Scala uniquement (si cela ne vous dérange pas d'exiger la bibliothèque de compilateur Scala):

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

Sinon, la solution de @ Renaud est courte et douce (si cela ne vous dérange pas de tirer dans Apache Commons FileUtils):

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

direst un fichier java.io.File:

new File("path/to/dir")
Brent Faust
la source
1

Il semble que personne ne mentionne la scala-iobibliothèque de scala-incubrator ...

import scalax.file.Path

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

Ou avec implicit

import scalax.file.ImplicitConversions.string2path

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

Ou si vous voulez implicitexplicitement ...

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

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

La documentation est disponible ici: http://jesseeichar.github.io/scala-io-doc/0.4.3/index.html#!/file/glob_based_path_sets

dessiner
la source
0

Cette incantation fonctionne pour moi:

  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
la source
0

Vous pouvez utiliser la récursivité de queue pour cela:

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
la source
-1

Pourquoi utilisez-vous le fichier Java au lieu du AbstractFile de Scala?

Avec AbstractFile de Scala, la prise en charge des itérateurs permet d'écrire une version plus concise de la solution de 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)
    }
Nicolas Rouquette
la source