Scala: Schreiben Sie einen String in eine Anweisung in eine Datei

144

Zum Lesen von Dateien in Scala gibt es

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

Gibt es eine äquivalente und präzise Möglichkeit, einen String in eine Datei zu schreiben?

Die meisten Sprachen unterstützen so etwas. Mein Favorit ist Groovy:

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

Ich möchte den Code für Programme verwenden, die von einer einzelnen Zeile bis zu einer kurzen Codepage reichen. Eine eigene Bibliothek benutzen zu müssen, macht hier keinen Sinn. Ich erwarte von einer modernen Sprache, dass ich bequem etwas in eine Datei schreiben kann.

Es gibt ähnliche Beiträge, aber sie beantworten meine genaue Frage nicht oder konzentrieren sich auf ältere Scala-Versionen.

Beispielsweise:

smartnut007
quelle
Siehe diese Frage. Ich stimme der am besten bewerteten Antwort zu - es ist besser, eine eigene Bibliothek zu haben.
Freund
2
Für diesen Fall stimme ich nicht zu, dass man seine eigene persönliche Bibliothek schreiben muss. Normalerweise, wenn Sie kleine Programmteile für Ad-hoc-Aufgaben schreiben (vielleicht möchte ich es nur als einseitiges Scala-Skript oder einfach in REPL schreiben). Dann wird der Zugriff auf eine persönliche Bibliothek zum Problem.
smartnut007
Grundsätzlich sieht es so aus, als ob es in Scala 2.9 derzeit nichts dafür gibt. Ich bin mir nicht sicher, ob ich diese Frage offen halten soll.
smartnut007
1
Wenn Sie im Scala-Quellcode nach java.io suchen, werden Sie nicht viele Vorkommen finden, und noch weniger für Ausgabeoperationen, insbesondere den PrintWriter. Bis die Scala-IO-Bibliothek offizieller Bestandteil von Scala wird, müssen wir reines Java verwenden, wie das Paradigma zeigt.
PhiLho
Ja, wahrscheinlich brauchen Sie auch ein Scala-Io, das mit JDK7 IO-Verbesserungen kompatibel ist.
smartnut007

Antworten:

77

Eine kurze Zeile:

import java.io.PrintWriter
new PrintWriter("filename") { write("file contents"); close }
LRLucena
quelle
14
Dieser Ansatz sieht zwar gut aus, ist jedoch weder ausnahmesicher noch codierungssicher. Wenn eine Ausnahme auftritt write(), closewird diese niemals aufgerufen und die Datei wird nicht geschlossen. PrintWriterVerwendet auch die Standardsystemcodierung, die für die Portabilität sehr schlecht ist. Und schließlich generiert dieser Ansatz eine separate Klasse speziell für diese Zeile (da Scala jedoch selbst für einen einfachen Code bereits Tonnen von Klassen generiert, ist dies an sich kaum ein Nachteil).
Vladimir Matveev
Gemäß dem obigen Kommentar ist dies zwar ein Einzeiler, aber unsicher. Wenn Sie mehr Sicherheit wünschen, während Sie mehr Optionen für die Position haben und / oder die Eingabe puffern
möchten,
2
Für die temporäre Debug-Protokollierung habe ichnew PrintWriter(tmpFile) { try {write(debugmsg)} finally {close} }
Randall Whitman
Stilistisch ist es wahrscheinlich besser zu verwenden close(), denke ich
Casey
Dies sind zwei Zeilen und können leicht wie new java.io.PrintWriter("/tmp/hello") { write("file contents"); close }
folgt
163

Es ist seltsam, dass niemand NIO.2-Operationen vorgeschlagen hat (verfügbar seit 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))

Ich denke, dies ist bei weitem der einfachste und einfachste und idiomatischste Weg, und es werden keine Abhängigkeiten ohne Java selbst benötigt.

Vladimir Matveev
quelle
Dies ignoriert aus irgendeinem Grund das Zeilenumbruchzeichen.
Kakaji
@ Kakaji, könnten Sie bitte näher darauf eingehen? Ich habe es gerade mit Strings mit Zeilenumbrüchen getestet und es funktioniert perfekt. Es kann einfach nichts filtern - Files.write () schreibt das Byte-Array als Blob, ohne dessen Inhalt zu analysieren. Schließlich kann in einigen Binärdaten 0x0d Byte eine andere wichtige Bedeutung als Zeilenumbruch haben.
Vladimir Matveev
4
Zusätzlicher Hinweis: Wenn Sie eine Datei haben, klicken Sie einfach auf ".toPath", um den ersten Parameter abzurufen.
Akauppi
1
Dieser ist eher industrietauglich (aufgrund der expliziten Auswahl von CharSets), aber es fehlt ihm die Einfachheit (und ein Liner ..) des reflect.io.File.writeAll(contents). Warum? Drei Zeilen, wenn Sie die beiden Importanweisungen einfügen. Vielleicht erledigt die IDE dies automatisch für Sie, aber wenn es in der REPL nicht so einfach ist.
Javadba
3
@javadba Wir sind in der JVM, Importe zählen so gut wie nicht als "Zeilen", insbesondere weil die Alternative fast immer das Hinzufügen einer neuen Bibliotheksabhängigkeit ist. Wie Files.writeauch immer, akzeptiert auch a java.lang.Iterableals zweites Argument, und wir können das von einer Scala Iterable, dh so ziemlich jedem Sammlungstyp, mit einem Konverter erhalten: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

Hier ist ein prägnanter Einzeiler mit Reflect.io.file, der mit Scala 2.12 funktioniert:

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

Wenn Sie die Java-Bibliotheken verwenden möchten, können Sie alternativ diesen Hack ausführen:

Some(new PrintWriter("filename")).foreach{p => p.write("hello world"); p.close}
Garrett Hall
quelle
1
+1 Funktioniert super. Die Some/ foreachCombo ist ein bisschen funky, hat aber den Vorteil, dass kein Versuch / Fang / Endlich möglich ist.
Brent Faust
3
Wenn der Schreibvorgang eine Ausnahme auslöst, möchten Sie möglicherweise die Datei schließen, wenn Sie die Ausnahme wiederherstellen und die Datei erneut lesen / schreiben möchten. Glücklicherweise bietet scala auch Einzeiler dafür an.
Garrett Hall
25
Dies wird nicht empfohlen, da das Paket scala.tools.nsc.io nicht Teil der öffentlichen API ist, sondern vom Compiler verwendet wird.
Giovanni Botta
3
Der Some/ foreachhack ist genau der Grund, warum viele Leute Scala für den unlesbaren Code hassen, den Hacker produzieren.
Erik Kaplun
3
scala.tootls.nsc.io.Fileist ein Alias ​​für die reflect.io.File. Immer noch interne API, aber zumindest etwas kürzer.
Kostja
41

Wenn Sie die Groovy-Syntax mögen, können Sie das Entwurfsmuster Pimp-My-Library verwenden, um es zu Scala zu bringen:

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 )

}

Es wird wie erwartet funktionieren:

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
paradigmatisch
quelle
2
Sie rufen niemals close für die von Source.fromFile zurückgegebene Instanz auf, was bedeutet, dass die Ressource erst geschlossen wird, wenn sie GCed (Garbage Collected) ist. Und Ihr PrintWriter ist nicht gepuffert, sodass er den winzigen JVM-Standardpuffer von 8 Byte verwendet, wodurch Ihre E / A möglicherweise erheblich verlangsamt wird. Ich habe gerade eine Antwort auf einen ähnlichen Thread erstellt, der sich mit diesen Problemen befasst: stackoverflow.com/a/34277491/501113
chaotic3quilibrium
1
Du hast recht. Aber die Lösung, die ich hier gegeben habe, funktioniert gut für kurzlebige Programme mit kleinen E / A. Ich empfehle es nicht für Serverprozesse oder große Datenmengen (als Faustregel mehr als 500 MB).
Paradigmatischer
23
import sys.process._
"echo hello world" #> new java.io.File("/tmp/example.txt") !
xiefei
quelle
Bei der REPL funktioniert das bei mir nicht. Keine Fehlermeldung, aber wenn ich mir /tmp/example.txt ansehe, gibt es keine.
Benutzer unbekannt
@Benutzer unbekannt, Entschuldigung, dass Sie das '!' am Ende des Befehls jetzt behoben.
Xiefei
Wunderbar! Jetzt, wo es funktioniert, würde ich gerne wissen warum. Was ist #>, was macht das !?
Benutzer unbekannt
10
Diese Lösung ist nicht portabel! funktioniert nur in * nix Systemen.
Giovanni Botta
2
Dies funktioniert nicht zum Schreiben beliebiger Zeichenfolgen. Es funktioniert nur für kurze Zeichenfolgen, die Sie als Argumente an das Befehlszeilentool übergeben können echo.
Rich
15

Eine Mikrobibliothek, die ich geschrieben habe: https://github.com/pathikrit/better-files

file.write("Hi!")

oder

file << "Hi!"
Pathikrit
quelle
3
Liebe deine Bibliothek! Diese Frage ist einer der Top-Hits beim Googeln, wie man eine Datei mit Scala schreibt. Jetzt, da Ihr Projekt größer geworden ist, möchten Sie vielleicht Ihre Antwort ein wenig erweitern?
Asac
12

Sie können Apache File Utils problemlos verwenden . Schauen Sie sich die Funktion an writeStringToFile. Wir verwenden diese Bibliothek in unseren Projekten.

RyuuGan
quelle
3
Ich benutze es auch die ganze Zeit. Wenn Sie die Frage sorgfältig lesen, habe ich bereits festgestellt, warum ich keine Bibliothek verwenden möchte.
smartnut007
Ohne Verwendung einer Bibliothek habe ich eine Lösung erstellt, die Ausnahmen beim Lesen / Schreiben behandelt UND über die winzigen Pufferstandards der Java-Bibliotheken hinaus gepuffert werden kann: stackoverflow.com/a/34277491/501113
chaotic3quilibrium
7

Man hat auch dieses Format, das sowohl prägnant ist als auch die zugrunde liegende Bibliothek wunderschön geschrieben ist (siehe Quellcode):

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!")
Dibbeke
quelle
7

Das ist kurz genug, denke ich:

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()
Luigi Sgro
quelle
4
Fair genug, aber dieses "prägnant genug" klassifiziert nicht als "eine Aussage": P
Erik Kaplun
Dieser Code eptimoziert viele der wahrgenommenen Probleme von Java. Leider hält Scala IO nicht für wichtig genug, sodass die Standardbibliothek keine enthält.
Chris
Die Antwort verbirgt Probleme mit verwaisten Ressourcen mit dem neuen FileWriter. Wenn der neue FileWriter erfolgreich ist, der neue BufferedWriter jedoch fehlschlägt, wird die FileWriter-Instanz nie wieder angezeigt und bleibt bis zum GCed (Garbage Collected) offen und kann auch dann nicht geschlossen werden (aufgrund der Art und Weise, wie die Garantien für die Finalisierung in der JVM funktionieren). Ich habe eine Antwort auf eine ähnliche Frage geschrieben, die diese Probleme behebt
chaotic3quilibrium
2

Sie können dies mit einer Mischung aus Java- und Scala-Bibliotheken tun. Sie haben die volle Kontrolle über die Zeichenkodierung. Leider werden die Dateihandles nicht richtig geschlossen.

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"))
stefan.schwetschke
quelle
Sie haben ein Problem mit verwaisten Ressourceninstanzen in Ihrem Code. Da Sie die Instanzen vor Ihrem Aufruf nicht erfassen, werden die Ressourcen, die erfolgreich instanziiert wurden, erst nach dem GCed (Garbage Collected) und sogar geschlossen, wenn eine Ausnahme ausgelöst wird, bevor die von Ihnen aufgerufene Methode die Parameter übergeben hat Dies liegt möglicherweise nicht an der Art und Weise, wie GC die Arbeit garantiert. Ich habe eine Antwort auf eine ähnliche Frage geschrieben, die diese Probleme behebt
chaotic3quilibrium
1
Sie haben Recht und Ihre Lösung ist ganz nett. Aber hier war die Anforderung, es in einer Zeile zu tun. Und wenn Sie sorgfältig lesen, habe ich das Ressourcenleck in meiner Antwort als Einschränkung erwähnt, die mit dieser Anforderung und meinem Ansatz einhergeht. Ihre Lösung ist nett, würde aber nicht dieser einzeiligen Anforderung entsprechen.
stefan.schwetschke
2

Ich weiß, dass es nicht eine Zeile ist, aber es löst die Sicherheitsprobleme, soweit ich das beurteilen kann.

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

Wenn Sie sich nicht um die Ausnahmebehandlung gekümmert haben, können Sie schreiben

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

UPDATE: Ich habe seitdem eine effektivere Lösung erstellt, auf die ich hier näher eingegangen bin: https://stackoverflow.com/a/34277491/501113

Ich arbeite immer mehr im Scala-Arbeitsblatt innerhalb der Scala-IDE für Eclipse (und ich glaube, dass IntelliJ IDEA etwas Äquivalentes enthält). Wie auch immer, ich muss in der Lage sein, einen Einzeiler zu erstellen, um einen Teil des Inhalts auszugeben, wenn ich die Meldung "Ausgabe überschreitet Grenzwert" erhalte. Nachricht, wenn ich etwas Bedeutendes tue, insbesondere mit den Scala-Sammlungen.

Ich habe mir einen Einzeiler ausgedacht, den ich oben in jedes neue Scala-Arbeitsblatt eingefügt habe, um dies zu vereinfachen (und daher muss ich nicht die gesamte Übung zum Importieren externer Bibliotheken für einen sehr einfachen Bedarf durchführen). Wenn Sie ein Stickler sind und feststellen, dass es sich technisch gesehen um zwei Zeilen handelt, dient dies nur dazu, die Lesbarkeit in diesem Forum zu verbessern. Es ist eine einzelne Zeile in meinem Scala-Arbeitsblatt.

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

Und die Verwendung ist einfach:

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

Auf diese Weise kann ich optional den Dateinamen angeben, falls ich zusätzliche Dateien über den Standard hinaus haben möchte (wodurch die Datei bei jedem Aufruf der Methode vollständig überschrieben wird).

Die zweite Verwendung ist also einfach:

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

Genießen!

chaotisches Gleichgewicht
quelle
1

Verwenden Sie die Ammonite Ops Library. Die Syntax ist sehr minimal, aber die Breite der Bibliothek ist fast so groß, wie man es erwarten würde, wenn man eine solche Aufgabe in einer Shell-Skriptsprache wie bash versucht.

Auf der Seite, auf die ich verlinkt habe, werden zahlreiche Vorgänge angezeigt, die mit der Bibliothek ausgeführt werden können. Um diese Frage zu beantworten, ist dies ein Beispiel für das Schreiben in eine Datei

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

Durch die Magie des Semikolons können Sie alles, was Sie möchten, zu einem Einzeiler machen.

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()
Ion Freeman
quelle
Kein Argument hier. Ich habe beschlossen, mich nicht daran zu erinnern, welche APIs mich benötigen, um einen Teil meines Lebens zu spülen, also mache ich es einfach immer.
Ion Freeman