Scala: escribe una cadena en un archivo en una declaración


144

Para leer archivos en Scala, hay

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

¿Hay una manera concisa y equivalente de escribir una cadena en el archivo?

La mayoría de los idiomas admiten algo así. Mi favorito es Groovy:

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

Me gustaría usar el código para programas que van desde una sola línea hasta una página corta de código. Tener que usar tu propia biblioteca no tiene sentido aquí. Espero que un lenguaje moderno me permita escribir algo en un archivo convenientemente.

Hay publicaciones similares a esta, pero no responden a mi pregunta exacta o se centran en versiones anteriores de Scala.

Por ejemplo:


Ver esta pregunta Estoy de acuerdo con la respuesta mejor calificada: es mejor tener su propia biblioteca personal.
amigo

2
para este caso no estoy de acuerdo en que uno tenga que escribir su propia biblioteca personal. Por lo general, cuando escribe pequeños programas para hacer cosas ad hoc (tal vez solo quiero escribirlo como un script de escala de una sola página o solo en REPL). Entonces acceder a una biblioteca personal se convierte en un dolor.
smartnut007

Básicamente, parece que no hay nada en scala 2.9 para esto en este momento. No estoy seguro de si debería mantener esta pregunta abierta.
smartnut007

1
Si busca java.io en el código fuente de Scala, no encontrará muchas ocurrencias, y menos aún para las operaciones de salida, particularmente PrintWriter. Entonces, hasta que la biblioteca Scala-IO se convierta en parte oficial de Scala, tenemos que usar Java puro, como lo demuestra paradigmático.
PhiLho

sí, prob también necesita un scala-io que sea compatible con las mejoras jdk7 IO.
smartnut007

Respuestas:


77

Una línea concisa:

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

14
Si bien este enfoque se ve bien, no es seguro para excepciones ni para codificación. Si ocurre una excepción write(), closenunca se llamará y el archivo no se cerrará. PrintWritertambién usa la codificación predeterminada del sistema, que es muy mala para la portabilidad. Y finalmente, este enfoque genera una clase separada específicamente para esta línea (sin embargo, dado que Scala ya genera toneladas de clases incluso para un código simple, esto no es un inconveniente en sí mismo).
Vladimir Matveev

Según el comentario anterior, si bien este es un trazador de líneas, no es seguro. Si desea más seguridad mientras tiene más opciones de ubicación y / o almacenando la entrada en el búfer, vea la respuesta que acabo de publicar en un hilo similar: stackoverflow.com/a/34277491/501113
chaotic3quilibrium

2
Para el registro de depuración temporal que utilicénew PrintWriter(tmpFile) { try {write(debugmsg)} finally {close} }
Randall Whitman,

Estilísticamente, probablemente sea mejor usar close(), creo
Casey

Estas son dos líneas y se pueden convertir fácilmente en una línea como esta:new java.io.PrintWriter("/tmp/hello") { write("file contents"); close }
Philluminati

163

Es extraño que nadie haya sugerido operaciones NIO.2 (disponible desde 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))

Creo que esta es, con mucho, la forma más simple, fácil e idiomática, y no necesita ninguna dependencia sin Java.


Esto ignora el carácter de nueva línea por alguna razón.
Kakaji

@Kakaji, ¿podrías explicarlo? Lo acabo de probar con cadenas con líneas nuevas y funciona perfectamente. Simplemente no puede filtrar nada: Files.write () escribe la matriz de bytes como un blob, sin analizar su contenido. Después de todo, en algunos datos binarios, el byte 0x0d puede tener un significado importante además de la nueva línea.
Vladimir Matveev

44
Nota adicional: si tiene un archivo, simplemente '.toPath' para obtener el primer parámetro.
akauppi

1
Este es más de grado industrial (debido a la elección explícita de CharSets) pero carece de la simplicidad (y un revestimiento ..) del reflect.io.File.writeAll(contents). ¿Por qué? Tres líneas cuando incluye las dos declaraciones de importación. Tal vez el IDE lo haga por usted automáticamente, pero si en el REPL no es tan fácil.
javadba

3
@javadba estamos en la JVM, las importaciones prácticamente no cuentan como 'líneas', especialmente porque la alternativa casi siempre es agregar una nueva dependencia de la biblioteca. De todos modos, Files.writetambién acepta un java.lang.Iterablecomo el segundo argumento, y podemos obtenerlo de una Scala Iterable, es decir, casi cualquier tipo de colección, utilizando un convertidor: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

Aquí hay una línea concisa que usa reflect.io.file, esto funciona con Scala 2.12:

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

Alternativamente, si desea utilizar las bibliotecas de Java, puede hacer este truco:

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

1
+1 Funciona muy bien. El Some/ foreachcombo es un poco funky, pero viene con el beneficio de no intentar / atrapar / finalmente.
Brent Faust

3
Bueno, si la escritura arroja una excepción, es posible que desee cerrar el archivo si planea recuperarse de la excepción y leer / escribir el archivo nuevamente. Afortunadamente, scala también ofrece frases para hacerlo.
Garrett Hall

25
Esto no se recomienda ya que el paquete scala.tools.nsc.io no forma parte de la API pública, sino que lo utiliza el compilador.
Giovanni Botta

3
El Some/ foreachhack es exactamente por qué muchas personas odian a Scala por el código ilegible que hace que los hackers produzcan.
Erik Kaplun

3
scala.tootls.nsc.io.Filees un alias para el reflect.io.File. Todavía API interna, pero al menos un poco más corta.
kostja

41

Si le gusta la sintaxis de Groovy, puede usar el patrón de diseño Pimp-My-Library para llevarlo a 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 )

}

Funcionará como se esperaba:

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
Nunca llama a cerrar en la instancia devuelta de Source.fromFile, lo que significa que el recurso no se cierra hasta que se GCed (basura recolectada). Y su PrintWriter no está almacenado en el búfer, por lo que está utilizando el pequeño búfer predeterminado JVM de 8 bytes, lo que puede ralentizar significativamente su E / S. Acabo de crear una respuesta en un hilo similar que trata estos problemas: stackoverflow.com/a/34277491/501113
chaotic3quilibrium

1
Tienes razón. Pero la solución que proporcioné aquí funciona bien para programas de corta duración con pequeñas E / S. No lo recomiendo para el proceso del servidor o los datos de gran tamaño (como regla general, más de 500 MB).
paradigmático

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

No me funciona en el REPL. No hay mensaje de error, pero si miro /tmp/example.txt no hay.
usuario desconocido

@usuario desconocido, perdón por perderte el '!' al final del comando, arreglado ahora.
xiefei

¡Maravilloso! Ahora que funciona, me gustaría saber por qué. ¿Qué es #>, qué hace !?
usuario desconocido

10
¡Esta solución no es portátil! solo funciona en sistemas * nix.
Giovanni Botta

2
Esto no funcionará para escribir cadenas arbitrarias. Solo funcionará para cadenas cortas que puede pasar como argumentos a la echoherramienta de línea de comandos.
Rico

15

Una micro biblioteca que escribí: https://github.com/pathikrit/better-files

file.write("Hi!")

o

file << "Hi!"

3
¡Ama tu biblioteca! Esta pregunta es uno de los principales éxitos al buscar en Google cómo escribir un archivo con scala: ahora que su proyecto se ha hecho más grande, ¿es posible que desee ampliar un poco su respuesta?
asac

12

Puede usar fácilmente Apache File Utils . Mira la función writeStringToFile. Usamos esta biblioteca en nuestros proyectos.


3
Lo uso todo el tiempo también. Si lees la pregunta detenidamente, ya he mencionado por qué no quiero usar una biblioteca.
smartnut007

Sin usar una biblioteca, he creado una solución que maneja las excepciones durante las lecturas / escrituras Y se puede almacenar más allá de los pequeños valores predeterminados del búfer proporcionados por las bibliotecas Java: stackoverflow.com/a/34277491/501113
chaotic3quilibrium

7

Uno también tiene este formato, que es conciso y la biblioteca subyacente está bellamente escrita (vea el código fuente):

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

Esto es bastante conciso, supongo:

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

44
Es justo, pero este "lo suficientemente conciso" no clasifica como "una declaración": P
Erik Kaplun

Este código eptimozes muchos de los problemas percibidos de Java. Desafortunadamente, Scala no considera que IO sea lo suficientemente importante, por lo que la biblioteca estándar no incluye uno.
Chris

La respuesta oculta problemas de recursos huérfanos con el nuevo FileWriter. Si el nuevo FileWriter tiene éxito, pero el nuevo BufferedWriter falla, la instancia de FileWriter nunca se vuelve a ver y permanece abierta hasta GCed (Recolección de basura), y es posible que no se cierre incluso debido a la forma en que finalize garantiza el trabajo en la JVM. He escrito una respuesta a una pregunta similar que aborda estos problemas: stackoverflow.com/a/34277491/501113
chaotic3quilibrium

2

Puede hacer esto con una combinación de bibliotecas Java y Scala. Tendrá control total sobre la codificación de caracteres. Pero desafortunadamente, los identificadores de archivo no se cerrarán correctamente.

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

Tiene un problema de instancia de recurso huérfano en su código. Dado que no está capturando las instancias antes de su llamada, si cualquiera de los dos arroja una excepción antes de que el método al que está llamando haya pasado los parámetros, los recursos que instanciaron con éxito no se cerrarán hasta el GCed (recolección de basura) e incluso entonces puede no ser debido a la forma en que GC garantiza el trabajo. He escrito una respuesta a una pregunta similar que aborda estos problemas: stackoverflow.com/a/34277491/501113
chaotic3quilibrium

1
Tienes razón y tu solución es bastante buena. Pero aquí el requisito era hacerlo en una línea. Y cuando lees detenidamente, he mencionado la pérdida de recursos en mi respuesta como una limitación que viene con este requisito y con mi enfoque. Su solución es buena, pero no coincidiría con ese requisito de una línea.
stefan.schwetschke

2

Sé que no es una línea, pero resuelve los problemas de seguridad por lo que puedo ver;

// 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 no le importó el manejo de excepciones, puede escribir

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

1

ACTUALIZACIÓN: desde entonces he creado una solución más efectiva sobre la cual he elaborado aquí: https://stackoverflow.com/a/34277491/501113

Me encuentro trabajando cada vez más en la Hoja de trabajo de Scala dentro del IDE de Scala para Eclipse (y creo que hay algo equivalente en IntelliJ IDEA). De todos modos, necesito poder hacer una línea para generar algunos de los contenidos a medida que obtengo el mensaje "La salida excede el límite de corte". mensaje si estoy haciendo algo significativo, especialmente con las colecciones Scala.

Se me ocurrió una línea que inserto en la parte superior de cada nueva hoja de trabajo de Scala para simplificar esto (y, por lo tanto, no tengo que hacer todo el ejercicio de importación de la biblioteca externa para una necesidad muy simple). Si eres un fanático y notas que técnicamente son dos líneas, es solo para hacerlo más legible en este foro. Es una sola línea en mi Hoja de trabajo de 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}}

Y el uso es simplemente:

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

Esto me permite proporcionar opcionalmente el nombre del archivo si deseo tener archivos adicionales más allá del valor predeterminado (que sobrescribe completamente el archivo cada vez que se llama al método).

Entonces, el segundo uso es simplemente:

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

¡Disfrutar!


1

Utilice la biblioteca de operaciones de ammonite . La sintaxis es muy mínima, pero la amplitud de la biblioteca es casi tan amplia como lo que cabría esperar al intentar tal tarea en un lenguaje de scripting de shell como bash.

En la página a la que enlacé, muestra numerosas operaciones que uno puede hacer con la biblioteca, pero para responder a esta pregunta, este es un ejemplo de cómo escribir en un archivo

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

-1

A través de la magia del punto y coma, puedes hacer lo que quieras con un solo trazo.

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

No hay discusión aquí. Decidí no recordar qué API me necesitan para eliminar parte de mi vida, así que siempre lo hago.
Ion Freeman
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.