Scala: scrivere una stringa su un file in un'istruzione


144

Per leggere i file in Scala, c'è

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

Esiste un modo equivalente e conciso per scrivere una stringa su file?

Molte lingue supportano qualcosa del genere. Il mio preferito è Groovy:

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

Vorrei usare il codice per programmi che vanno da una singola riga a una breve pagina di codice. Dover usare la tua libreria non ha senso qui. Mi aspetto che un linguaggio moderno mi permetta di scrivere comodamente qualcosa su un file.

Ci sono post simili a questo, ma non rispondono alla mia domanda esatta o si concentrano su versioni precedenti di Scala.

Per esempio:


Vedi questa domanda Sono d'accordo con la risposta più votata - è meglio avere la tua biblioteca personale.
amico il

2
per questo caso non sono d'accordo che si debba scrivere la propria biblioteca personale. Di solito, quando scrivi piccoli programmi per fare cose ad hoc (forse voglio solo scriverlo come uno script scala a pagina singola o semplicemente in REPL). Quindi accedere a una biblioteca personale diventa un dolore.
smartnut007,

Fondamentalmente, sembra che non ci sia nulla in Scala 2.9 per questo a questo punto. Non so come se dovessi tenere aperta questa domanda.
smartnut007

1
Se cerchi java.io nel codice sorgente di Scala, non troverai molte occorrenze e ancor meno operazioni di output, in particolare PrintWriter. Quindi, fino a quando la libreria Scala-IO non diventerà parte ufficiale di Scala, dobbiamo usare Java puro, come mostrato da paradigmatic.
PhiLho,

Sì, probabilmente è necessario anche uno scala-io compatibile con i miglioramenti IO jdk7.
smartnut007,

Risposte:


77

Una riga concisa:

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

14
Sebbene questo approccio sia gradevole, non è né sicuro dalle eccezioni né sicuro per la codifica. Se si verifica un'eccezione write(), closenon verrà mai chiamato e il file non verrà chiuso. PrintWriterutilizza anche la codifica di sistema predefinita, che è molto male per la portabilità. E infine, questo approccio genera una classe separata appositamente per questa linea (tuttavia, dato che Scala genera già tonnellate di classi anche per un semplice codice, questo non è certo uno svantaggio in sé).
Vladimir Matveev,

Per il commento sopra, mentre questo è un solo rivestimento, non è sicuro. Se si vuole più sicurezza, pur avendo più opzioni in giro per posizione e / o il buffering l'ingresso, vedere la risposta che ho appena postato su un thread simile: stackoverflow.com/a/34277491/501113
chaotic3quilibrium

2
Per il debug-logging temporaneo ho usatonew PrintWriter(tmpFile) { try {write(debugmsg)} finally {close} }
Randall Whitman

Stilisticamente è probabilmente meglio usare close(), penso
Casey il

Si tratta di due righe e può essere facilmente trasformata in una riga in questo modo:new java.io.PrintWriter("/tmp/hello") { write("file contents"); close }
Philluminati

163

È strano che nessuno abbia suggerito operazioni NIO.2 (disponibili da 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))

Penso che questo sia di gran lunga il modo più semplice, più semplice e più idiomatico, e non ha bisogno di dipendenze rispetto a Java stesso.


Questo ignora il carattere di nuova riga per qualche motivo.
Kakaji,

@Kakaji, potresti per favore elaborare? L'ho appena testato con stringhe con newline e funziona perfettamente. Semplicemente non può filtrare nulla - Files.write () scrive l'array di byte come un BLOB, senza analizzarne il contenuto. Dopotutto, in alcuni dati binari 0x0d byte può avere un significato importante diverso da newline.
Vladimir Matveev,

4
Nota aggiuntiva: se hai un file, basta '.toPath' per ottenere il primo parametro.
akauppi,

1
Questo è più di livello industriale (a causa della scelta esplicita di CharSet) ma manca della semplicità (e di un liner ...) di reflect.io.File.writeAll(contents). Perché? Tre righe quando includi le due dichiarazioni di importazione. Forse l'IDE lo fa automaticamente per te, ma se nel REPL non è così facile.
Javavba,

3
@javadba siamo nella JVM, le importazioni praticamente non contano come 'linee' soprattutto perché l'alternativa è quasi sempre l'aggiunta di una nuova dipendenza dalla libreria. Ad ogni modo, Files.writeaccetta anche a java.lang.Iterablecome secondo argomento, e possiamo ottenerlo da una Scala Iterable, cioè praticamente qualsiasi tipo di collezione, usando un convertitore: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

Ecco un conciso one-liner che utilizza reflection.io.file, funziona con Scala 2.12:

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

In alternativa, se si desidera utilizzare le librerie Java, è possibile eseguire questo hack:

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

1
+1 Funziona alla grande. La Some/ foreachcombo è un po 'funky, ma arriva con il vantaggio di non provare / catturare / finalmente.
Brent Faust

3
Bene, se la scrittura genera un'eccezione, potresti voler chiudere il file se hai intenzione di recuperare dall'eccezione e leggere / scrivere di nuovo il file. Fortunatamente scala fornisce anche una linea per questo.
Garrett Hall

25
Questo non è raccomandato poiché il pacchetto scala.tools.nsc.io non fa parte dell'API pubblica ma è usato dal compilatore.
Giovanni Botta,

3
Il Some/ foreachhack è esattamente il motivo per cui molte persone odiano Scala per il codice illeggibile che provoca la produzione di hacker.
Erik Kaplun,

3
scala.tootls.nsc.io.Fileè un alias per il reflect.io.File. API ancora interna, ma almeno un po 'più breve.
kostja,

41

Se ti piace la sintassi di Groovy, puoi usare il modello di progettazione Pimp-My-Library per portarlo su 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 )

}

Funzionerà come previsto:

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
Non chiamare mai vicino sull'istanza restituita da Source.fromFile, il che significa che la risorsa non viene chiusa fino a quando non viene GCed (Garbage Collected). E PrintWriter non è bufferizzato, quindi utilizza il piccolo buffer predefinito JVM di 8 byte, rallentando potenzialmente in modo significativo l'IO. Ho appena creato una risposta su un thread simile che si occupa di questi problemi: stackoverflow.com/a/34277491/501113
chaotic3quilibrium

1
Hai ragione. Ma la soluzione che ho dato qui funziona bene per programmi di breve durata con un piccolo IO. Non lo consiglio per processi server o dati di grandi dimensioni (di regola, più di 500 MB).
paradigmatico

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

Non funziona per me nel REPL. Nessun errore, ma se guardo /tmp/example.txt non c'è.
utente sconosciuto

@utente sconosciuto, ci scusiamo per aver perso "!" alla fine del comando, risolto ora.
xiefei,

Meraviglioso! Ora che funziona, vorrei sapere perché. Cosa è #>, cosa fa !?
utente sconosciuto

10
Questa soluzione non è portatile! funziona solo nei sistemi * nix.
Giovanni Botta,

2
Questo non funzionerà per la scrittura di stringhe arbitrarie. Funzionerà solo per stringhe brevi che è possibile passare come argomenti allo echostrumento da riga di comando .
Ricco

15

Una micro biblioteca che ho scritto: https://github.com/pathikrit/better-files

file.write("Hi!")

o

file << "Hi!"

3
Adoro la tua biblioteca! Questa domanda è uno dei maggiori successi quando si cerca su google come scrivere un file con scala - ora che il tuo progetto è diventato più grande, potresti voler espandere un po 'la tua risposta?
asac,

12

Puoi facilmente usare i file Apache Utils . Guarda la funzione writeStringToFile. Usiamo questa libreria nei nostri progetti.


3
Lo uso sempre. Se leggi attentamente la domanda, ho già accennato al motivo per cui non voglio usare una biblioteca.
smartnut007,

Senza l'utilizzo di una libreria, ho creato una soluzione che gestisce le eccezioni durante la lettura / scrittura ed è in grado di essere tamponato al di là dei piccoli valori di default del buffer forniti dalle librerie Java: stackoverflow.com/a/34277491/501113
chaotic3quilibrium

7

Uno ha anche questo formato, che è allo stesso tempo conciso e la libreria sottostante è magnificamente scritta (vedi il codice sorgente):

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

Questo è abbastanza conciso, immagino:

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
Abbastanza giusto, ma questo "abbastanza conciso" non si classifica come "una frase": P
Erik Kaplun,

Questo codice ottimizza molti dei problemi percepiti di Java. Sfortunatamente Scala non considera l'IO abbastanza importante, quindi la libreria standard non ne include una.
Chris,

La risposta nasconde problemi di risorse orfane con il nuovo FileWriter. Se il nuovo FileWriter ha esito positivo, ma il nuovo BufferedWriter ha esito negativo, l'istanza di FileWriter non viene mai più vista e rimane aperta fino a GCed (Garbage Collected) e potrebbe non essere chiusa anche in quel momento (a causa del modo in cui finalizza le garanzie funzionano nella JVM). Ho scritto una risposta a una domanda simile, che affronta questi temi: stackoverflow.com/a/34277491/501113
chaotic3quilibrium

2

Puoi farlo con un mix di librerie Java e Scala. Avrai il pieno controllo sulla codifica dei caratteri. Ma sfortunatamente, gli handle di file non verranno chiusi correttamente.

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

Hai un problema di istanza di risorsa orfana nel tuo codice. Dal momento che non si acquisiscono le istanze prima della chiamata, se si genera un'eccezione prima che al metodo che si sta chiamando siano stati passati i parametri, le risorse che sono state istanziate correttamente non saranno tutte chiuse fino a GCed (Garbage Collected) e persino quindi potrebbe non essere dovuto al modo in cui GC garantisce il lavoro. Ho scritto una risposta a una domanda simile, che affronta questi temi: stackoverflow.com/a/34277491/501113
chaotic3quilibrium

1
Hai ragione e la tua soluzione è abbastanza bella. Ma qui il requisito era farlo in una riga. E quando leggi attentamente, ho menzionato la perdita di risorse nella mia risposta come un limite che deriva da questo requisito e dal mio approccio. La tua soluzione è buona, ma non corrisponderebbe a quel requisito di una riga.
stefan.schwetschke,

2

So che non è una riga, ma risolve i problemi di sicurezza per quanto posso dire;

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

Se non ti interessa la gestione delle eccezioni, puoi scrivere

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

1

AGGIORNAMENTO: Da allora ho creato una soluzione più efficace su cui ho elaborato qui: https://stackoverflow.com/a/34277491/501113

Mi ritrovo a lavorare sempre di più nel foglio di lavoro Scala all'interno dell'IDE Scala per Eclipse (e credo che ci sia qualcosa di equivalente in IntelliJ IDEA). In ogni caso, devo essere in grado di fare un solo liner per produrre alcuni dei contenuti man mano che ottengo il "Output supera il limite di cutoff". messaggio se sto facendo qualcosa di significativo, specialmente con le collezioni Scala.

Mi è venuta in mente una riga che inserisco nella parte superiore di ogni nuovo foglio di lavoro Scala per semplificare ciò (e quindi non devo fare l'intero esercizio di importazione di librerie esterne per un bisogno molto semplice). Se sei un pignolo e noti che è tecnicamente due righe, è solo per renderlo più leggibile in questo forum. È una riga singola nel mio foglio di lavoro 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}}

E l'uso è semplicemente:

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

Questo mi permette di fornire facoltativamente il nome del file se voglio avere file aggiuntivi oltre il valore predefinito (che sovrascrive completamente il file ogni volta che viene chiamato il metodo).

Quindi, il secondo utilizzo è semplicemente:

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

Godere!


1

Usa la libreria di ammonite ops . La sintassi è molto minima, ma l'ampiezza della libreria è quasi ampia quanto quello che ci si aspetterebbe da tentare una simile attività in un linguaggio di script di shell come bash.

Nella pagina a cui mi sono collegato, mostra numerose operazioni che è possibile eseguire con la libreria, ma per rispondere a questa domanda, questo è un esempio di scrittura su un file

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

-1

Attraverso la magia del punto e virgola, puoi creare qualsiasi cosa ti piaccia una fodera.

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

Nessun argomento qui. Ho deciso di non ricordare quali API hanno bisogno di me per cancellare parte della mia vita, quindi lo faccio sempre.
Ion Freeman,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.