Scala: écrire une chaîne dans un fichier en une seule instruction


144

Pour lire des fichiers dans Scala, il existe

Source.fromFile("file.txt").mkString

Existe-t-il un moyen équivalent et concis d'écrire une chaîne dans un fichier?

La plupart des langues prennent en charge quelque chose comme ça. Mon préféré est Groovy:

def f = new File("file.txt")
// Read
def s = f.text
// Write
f.text = "file contents"

J'aimerais utiliser le code pour des programmes allant d'une seule ligne à une courte page de code. Devoir utiliser votre propre bibliothèque n'a pas de sens ici. Je m'attends à ce qu'un langage moderne me permette d'écrire quelque chose dans un fichier facilement.

Il existe des articles similaires à celui-ci, mais ils ne répondent pas à ma question exacte ou sont axés sur les anciennes versions de Scala.

Par exemple:


Voir cette question. Je suis d'accord avec la réponse la mieux notée - il vaut mieux avoir sa propre bibliothèque personnelle.
ffriend

2
dans ce cas, je ne suis pas d'accord qu'il faille écrire sa propre bibliothèque personnelle. Habituellement, lorsque vous écrivez de petits morceaux de programmes pour faire des choses ad hoc (peut-être que je veux juste l'écrire comme un script scala d'une seule page ou simplement en REPL). Alors accéder à une bibliothèque personnelle devient une douleur.
smartnut007

Fondamentalement, il semble qu'il n'y ait rien dans scala 2.9 pour cela à ce stade. Je ne sais pas comment si je dois garder cette question ouverte.
smartnut007

1
Si vous recherchez java.io dans le code source de Scala, vous ne trouverez pas beaucoup d'occurrences, et encore moins pour les opérations de sortie, en particulier le PrintWriter. Donc, jusqu'à ce que la bibliothèque Scala-IO devienne une partie officielle de Scala, nous devons utiliser du Java pur, comme le montre paradigmatic.
PhiLho

oui, prob a également besoin d'un scala-io compatible avec les améliorations IO de jdk7.
smartnut007

Réponses:


77

Une ligne concise:

import java.io.PrintWriter
new PrintWriter("filename") { write("file contents"); close }

14
Bien que cette approche ait l'air agréable, elle n'est ni sûre pour les exceptions ni pour l'encodage. Si une exception se produit dans write(), closene sera jamais appelée et le fichier ne sera pas fermé. PrintWriterutilise également le codage système par défaut, ce qui est très mauvais pour la portabilité. Et enfin, cette approche génère une classe distincte spécifiquement pour cette ligne (cependant, étant donné que Scala génère déjà des tonnes de classes même pour un code simple, ce n'est guère un inconvénient en soi).
Vladimir Matveev

Selon le commentaire ci-dessus, bien qu'il s'agisse d'une seule ligne, ce n'est pas sûr. Si vous voulez plus de sécurité tout en ayant plus d'options autour de l'emplacement et / ou de la mise en mémoire tampon de l'entrée, voir la réponse que je viens de
publier

2
Pour la journalisation de débogage temporaire, j'ai utilisénew PrintWriter(tmpFile) { try {write(debugmsg)} finally {close} }
Randall Whitman

Stylistiquement, il est probablement préférable d'utiliser close(), je pense
Casey

Il s'agit de deux lignes et peut facilement être transformée en une seule ligne comme celle-ci:new java.io.PrintWriter("/tmp/hello") { write("file contents"); close }
Philluminati

163

Il est étrange que personne n'ait suggéré d'opérations NIO.2 (disponibles depuis Java 7):

import java.nio.file.{Paths, Files}
import java.nio.charset.StandardCharsets

Files.write(Paths.get("file.txt"), "file contents".getBytes(StandardCharsets.UTF_8))

Je pense que c'est de loin le moyen le plus simple et le plus simple et le plus idiomatique, et il n'a besoin d'aucune dépendance sans Java lui-même.


Cela ignore le caractère de nouvelle ligne pour une raison quelconque.
Kakaji

@Kakaji, pourriez-vous s'il vous plaît élaborer? Je viens de le tester avec des chaînes avec des nouvelles lignes, et cela fonctionne parfaitement. Il ne peut tout simplement rien filtrer - Files.write () écrit le tableau d'octets comme un objet blob, sans analyser son contenu. Après tout, dans certaines données binaires, l'octet 0x0d peut avoir une signification importante autre que le saut de ligne.
Vladimir Matveev

4
Note supplémentaire: si vous avez un fichier, juste '.toPath' pour obtenir le premier paramètre.
akauppi

1
Celui-ci est plus de qualité industrielle (en raison du choix explicite des CharSets) mais manque de la simplicité (et d'une doublure ..) du reflect.io.File.writeAll(contents). Pourquoi? Trois lignes lorsque vous incluez les deux instructions d'importation. Peut-être que l'EDI le fait automatiquement pour vous, mais si dans le REPL ce n'est pas aussi facile.
javadba

3
@javadba nous sommes dans la JVM, les importations ne comptent pratiquement pas comme des «lignes», surtout parce que l'alternative est presque toujours d'ajouter une nouvelle dépendance de bibliothèque. Quoi qu'il en soit, Files.writeaccepte également a java.lang.Iterablecomme deuxième argument, et nous pouvons l'obtenir à partir d'une Scala Iterable, c'est-à-dire à peu près n'importe quel type de collection, en utilisant un convertisseur:import java.nio.file.{Files, Paths}; import scala.collection.JavaConverters.asJavaIterableConverter; val output = List("1", "2", "3"); Files.write(Paths.get("output.txt"), output.asJava)
Yawar

83

Voici un one-liner concis utilisant reflect.io.file, cela fonctionne avec Scala 2.12:

reflect.io.File("filename").writeAll("hello world")

Alternativement, si vous souhaitez utiliser les bibliothèques Java, vous pouvez faire ce hack:

Some(new PrintWriter("filename")).foreach{p => p.write("hello world"); p.close}

1
+1 Fonctionne très bien. Le Some/ foreachcombo est un peu génial, mais il a l'avantage de ne pas essayer / attraper / enfin.
Brent Faust

3
Eh bien, si l'écriture lève une exception, vous pouvez fermer le fichier si vous prévoyez de récupérer de l'exception et de lire / écrire à nouveau le fichier. Heureusement, scala fournit également des one-liners pour faire cela.
Garrett Hall

25
Cela n'est pas recommandé car le package scala.tools.nsc.io ne fait pas partie de l'API publique mais est utilisé par le compilateur.
Giovanni Botta le

3
Le Some/ foreachhack est exactement la raison pour laquelle beaucoup de gens détestent Scala pour le code illisible qu'il fait produire par les pirates.
Erik Kaplun

3
scala.tootls.nsc.io.Fileest un alias pour le reflect.io.File. Toujours API interne, mais au moins un peu plus courte.
kostja

41

Si vous aimez la syntaxe Groovy, vous pouvez utiliser le modèle de conception Pimp-My-Library pour l'amener à Scala:

import java.io._
import scala.io._

class RichFile( file: File ) {

  def text = Source.fromFile( file )(Codec.UTF8).mkString

  def text_=( s: String ) {
    val out = new PrintWriter( file , "UTF-8")
    try{ out.print( s ) }
    finally{ out.close }
  }
}

object RichFile {

  implicit def enrichFile( file: File ) = new RichFile( file )

}

Cela fonctionnera comme prévu:

scala> import RichFile.enrichFile
import RichFile.enrichFile

scala> val f = new File("/tmp/example.txt")
f: java.io.File = /tmp/example.txt

scala> f.text = "hello world"

scala> f.text
res1: String = 
"hello world

2
Vous n'appelez jamais close sur l'instance renvoyée par Source.fromFile, ce qui signifie que la ressource n'est pas fermée tant qu'elle n'est pas GCed (Garbage Collected). Et votre PrintWriter n'est pas mis en mémoire tampon, il utilise donc le minuscule tampon par défaut JVM de 8 octets, ce qui ralentit potentiellement considérablement vos E / S. Je viens de créer une réponse sur un fil similaire qui traite de ces problèmes: stackoverflow.com/a/34277491/501113
chaotic3quilibrium

1
Vous avez raison. Mais la solution que j'ai donnée ici fonctionne bien pour les programmes de courte durée avec de petites E / S. Je ne le recommande pas pour les processus de serveur ou les données volumineuses (en règle générale, plus de 500 Mo).
paradigmatic

23
import sys.process._
"echo hello world" #> new java.io.File("/tmp/example.txt") !

Cela ne fonctionne pas pour moi dans la REPL. Pas de message d'erreur, mais si je regarde /tmp/example.txt, il n'y en a pas.
utilisateur inconnu le

@user inconnu, Désolé d'avoir raté le "!" à la fin de la commande, corrigé maintenant.
xiefei

Merveilleux! Maintenant que ça marche, j'aimerais savoir pourquoi. Qu'est- #>ce que c'est que !faire?
utilisateur inconnu le

10
Cette solution n'est pas portable! ne fonctionne que dans les systèmes * nix.
Giovanni Botta

2
Cela ne fonctionnera pas pour l'écriture de chaînes arbitraires. Cela ne fonctionnera que pour les chaînes courtes que vous pouvez transmettre comme arguments à l' echooutil de ligne de commande.
Rich

15

Une micro bibliothèque que j'ai écrite: https://github.com/pathikrit/better-files

file.write("Hi!")

ou

file << "Hi!"

3
Aimez votre bibliothèque! Cette question est l'un des meilleurs résultats lorsque vous recherchez sur Google comment écrire un fichier avec scala - maintenant que votre projet s'est agrandi, vous voudrez peut-être élargir un peu votre réponse?
asac

12

Vous pouvez facilement utiliser Apache File Utils . Regardez la fonction writeStringToFile. Nous utilisons cette bibliothèque dans nos projets.


3
Je l'utilise aussi tout le temps. Si vous lisez attentivement la question, j'ai déjà expliqué pourquoi je ne veux pas utiliser de bibliothèque.
smartnut007

Sans utiliser de bibliothèque, j'ai créé une solution qui gère les exceptions lors des lectures / écritures ET peut être mise en mémoire tampon au-delà des minuscules valeurs par défaut de tampon fournies par les bibliothèques Java: stackoverflow.com/a/34277491/501113
chaotic3quilibrium

7

On a aussi ce format, qui est à la fois concis et la bibliothèque sous-jacente est magnifiquement écrite (voir le code source):

import scalax.io.Codec
import scalax.io.JavaConverters._

implicit val codec = Codec.UTF8

new java.io.File("hi-file.txt").asOutput.write("I'm a hi file! ... Really!")

7

C'est assez concis, je suppose:

scala> import java.io._
import java.io._

scala> val w = new BufferedWriter(new FileWriter("output.txt"))
w: java.io.BufferedWriter = java.io.BufferedWriter@44ba4f

scala> w.write("Alice\r\nBob\r\nCharlie\r\n")

scala> w.close()

4
Assez juste, mais ce "assez concis" ne se classe pas comme "une déclaration": P
Erik Kaplun

Ce code résout bon nombre des problèmes perçus de Java. Malheureusement, Scala ne considère pas les E / S suffisamment importantes, donc la bibliothèque standard n'en contient pas.
Chris

La réponse cache des problèmes de ressources orphelines avec le nouveau FileWriter. Si le nouveau FileWriter réussit, mais que le nouveau BufferedWriter échoue, l'instance FileWriter n'est plus jamais vue et reste suspendue jusqu'à ce que GCed (Garbage Collected), et peut ne pas être fermée même alors (en raison de la façon dont finaliser les garanties fonctionnent dans la JVM). J'ai écrit une réponse à une question similaire qui aborde ces problèmes: stackoverflow.com/a/34277491/501113
chaotic3quilibrium

2

Vous pouvez le faire avec un mélange de bibliothèques Java et Scala. Vous aurez un contrôle total sur l'encodage des caractères. Mais malheureusement, les descripteurs de fichiers ne seront pas fermés correctement.

scala> import java.io.ByteArrayInputStream
import java.io.ByteArrayInputStream

scala> import java.io.FileOutputStream
import java.io.FileOutputStream

scala> BasicIO.transferFully(new ByteArrayInputStream("test".getBytes("UTF-8")), new FileOutputStream("test.txt"))

Vous avez un problème d'instance de ressource orpheline dans votre code. Puisque vous ne capturez pas les instances avant votre appel, si l'un ou l'autre lève une exception avant que la méthode que vous appelez n'ait reçu les paramètres, les ressources qui ont été instanciées avec succès ne seront pas toutes fermées jusqu'à ce que GCed (Garbage Collected), et même cela peut ne pas être dû au fonctionnement des garanties GC. J'ai écrit une réponse à une question similaire qui aborde ces problèmes: stackoverflow.com/a/34277491/501113
chaotic3quilibrium

1
Vous avez raison et votre solution est plutôt sympa. Mais ici, l'exigence était de le faire en une seule ligne. Et quand vous lisez attentivement, j'ai mentionné la fuite de ressources dans ma réponse comme une limitation qui vient avec cette exigence et avec mon approche. Votre solution est bonne, mais ne correspondrait pas à cette exigence d'une ligne.
stefan.schwetschke

2

Je sais que ce n'est pas une ligne, mais cela résout les problèmes de sécurité pour autant que je sache;

// This possibly creates a FileWriter, but maybe not
val writer = Try(new FileWriter(new File("filename")))
// If it did, we use it to write the data and return it again
// If it didn't we skip the map and print the exception and return the original, just in-case it was actually .write() that failed
// Then we close the file
writer.map(w => {w.write("data"); w}).recoverWith{case e => {e.printStackTrace(); writer}}.map(_.close)

Si vous ne vous souciez pas de la gestion des exceptions, vous pouvez écrire

writer.map(w => {w.writer("data"); w}).recoverWith{case _ => writer}.map(_.close)

1

MISE À JOUR: J'ai depuis créé une solution plus efficace sur laquelle j'ai élaboré ici: https://stackoverflow.com/a/34277491/501113

Je travaille de plus en plus dans la feuille de travail Scala dans l'EDI Scala pour Eclipse (et je crois qu'il y a quelque chose d'équivalent dans IntelliJ IDEA). Quoi qu'il en soit, je dois être capable de faire un one-liner pour sortir une partie du contenu lorsque j'obtiens le message "La sortie dépasse la limite de coupure". message si je fais quelque chose d'important, en particulier avec les collections Scala.

J'ai créé une ligne unique que j'insère en haut de chaque nouvelle feuille de travail Scala pour simplifier cela (et je n'ai donc pas à faire tout l'exercice d'importation de bibliothèque externe pour un besoin très simple). Si vous êtes un stickler et que vous remarquez qu'il s'agit techniquement de deux lignes, c'est uniquement pour le rendre plus lisible dans ce forum. C'est une seule ligne dans ma feuille de calcul Scala.

def printToFile(content: String, location: String = "C:/Users/jtdoe/Desktop/WorkSheet.txt") =
  Some(new java.io.PrintWriter(location)).foreach{f => try{f.write(content)}finally{f.close}}

Et l'utilisation est simplement:

printToFile("A fancy test string\ncontaining newlines\nOMG!\n")

Cela me permet de fournir éventuellement le nom du fichier si je veux avoir des fichiers supplémentaires au-delà de la valeur par défaut (qui écrase complètement le fichier à chaque fois que la méthode est appelée).

Donc, la deuxième utilisation est simplement:

printToFile("A fancy test string\ncontaining newlines\nOMG!\n", "C:/Users/jtdoe/Desktop/WorkSheet.txt")

Prendre plaisir!


1

Utilisez des opérations d'ammonite bibliothèque d' . La syntaxe est très minimale, mais l'étendue de la bibliothèque est presque aussi large que ce à quoi on pourrait s'attendre en tentant une telle tâche dans un langage de script shell comme bash.

Sur la page à laquelle j'ai lié, il montre de nombreuses opérations que l'on peut faire avec la bibliothèque, mais pour répondre à cette question, voici un exemple d'écriture dans un fichier

import ammonite.ops._
write(pwd/'"file.txt", "file contents")

-1

Grâce à la magie du point-virgule, vous pouvez créer tout ce que vous aimez en une seule ligne.

import java.io.PrintWriter
import java.nio.file.Files
import java.nio.file.Paths
import java.nio.charset.StandardCharsets
import java.nio.file.StandardOpenOption
val outfile = java.io.File.createTempFile("", "").getPath
val outstream = new PrintWriter(Files.newBufferedWriter(Paths.get(outfile)
  , StandardCharsets.UTF_8
  , StandardOpenOption.WRITE)); outstream.println("content"); outstream.flush(); outstream.close()

Aucun argument ici. J'ai décidé de ne pas me souvenir des API dont j'ai besoin pour vider une partie de ma vie, alors je le fais toujours.
Ion Freeman
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.