Scala: zapisz ciąg do pliku w jednej instrukcji

144

Do czytania plików w Scali jest

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

Czy istnieje równoważny i zwięzły sposób zapisania ciągu znaków do pliku?

Większość języków obsługuje coś takiego. Moim ulubionym jest Groovy:

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

Chciałbym użyć kodu dla programów obejmujących od pojedynczego wiersza do krótkiej strony kodu. Korzystanie z własnej biblioteki nie ma tutaj sensu. Oczekuję, że nowoczesny język pozwoli mi wygodnie napisać coś do pliku.

Są posty podobne do tego, ale nie odpowiadają one dokładnie na moje pytanie lub dotyczą starszych wersji Scali.

Na przykład:

smartnut007
źródło
Zobacz to pytanie. Zgadzam się z najwyżej ocenioną odpowiedzią - lepiej mieć własną bibliotekę.
ffriend
2
w tym przypadku nie zgadzam się, że trzeba pisać własną bibliotekę. Zwykle, gdy piszesz małe fragmenty programów do robienia rzeczy ad hoc (może po prostu chcę napisać to jako jednostronicowy skrypt scala lub po prostu w REPL). Wtedy dostęp do osobistej biblioteki staje się uciążliwy.
smartnut007
Zasadniczo wygląda na to, że w tym momencie w scali 2.9 nie ma nic do tego. Nie wiem, jak mam pozostawić to pytanie otwarte.
smartnut007
1
Jeśli szukasz java.io w kodzie źródłowym Scala, nie znajdziesz wielu wystąpień, a nawet mniej dla operacji wyjściowych, szczególnie PrintWriter. Tak więc, dopóki biblioteka Scala-IO nie stanie się oficjalną częścią Scali, musimy używać czystej Javy, jak pokazuje paradygmat.
PhiLho
tak, prawdopodobnie potrzebuję również scala-io, która jest kompatybilna z ulepszeniami we / wy jdk7.
smartnut007

Odpowiedzi:

77

Zwięzła jedna linia:

import java.io.PrintWriter
new PrintWriter("filename") { write("file contents"); close }
LRLucena
źródło
14
Chociaż to podejście wygląda ładnie, nie jest ani wyjątkowe, ani bezpieczne dla kodowania. Jeśli w programie wystąpi wyjątek write(), closenigdy nie zostanie wywołany, a plik nie zostanie zamknięty. PrintWriterużywa również domyślnego kodowania systemowego, co jest bardzo niekorzystne dla przenośności. I wreszcie, to podejście generuje oddzielną klasę specjalnie dla tej linii (jednak biorąc pod uwagę, że Scala już generuje mnóstwo klas nawet dla prostego kodu, nie jest to sama w sobie wada).
Vladimir Matveev
Zgodnie z powyższym komentarzem, chociaż jest to jedna linijka, jest niebezpieczna. Jeśli chcesz mieć większe bezpieczeństwo, mając więcej opcji dotyczących lokalizacji i / lub buforowania danych wejściowych, zobacz odpowiedź, którą właśnie opublikowałem w podobnym wątku: stackoverflow.com/a/34277491/501113
chaotic3quilibrium
2
Do tymczasowego logowania do debugowania użyłemnew PrintWriter(tmpFile) { try {write(debugmsg)} finally {close} }
Randall Whitman
Stylistycznie chyba lepiej jest użyć close(), myślę
Casey
To są dwie linie i można je łatwo przekształcić w jedną linię, na przykład:new java.io.PrintWriter("/tmp/hello") { write("file contents"); close }
Philluminati
163

To dziwne, że nikt nie zasugerował operacji NIO.2 (dostępnych od wersji 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))

Myślę, że jest to zdecydowanie najprostszy, najłatwiejszy i najbardziej idiomatyczny sposób, który nie wymaga żadnych zależności poza samą Javą.

Vladimir Matveev
źródło
Z jakiegoś powodu ignoruje to znak nowej linii.
Kakaji
@Kakaji, czy mógłbyś to rozwinąć? Właśnie przetestowałem to z ciągami znaków z nowymi liniami i działa idealnie. Po prostu nie może niczego filtrować - Files.write () zapisuje tablicę bajtów jako blob, bez analizowania jej zawartości. W końcu w niektórych danych binarnych bajt 0x0d może mieć ważne znaczenie inne niż znak nowej linii.
Vladimir Matveev
4
Dodatkowa uwaga: jeśli masz plik, po prostu „.toPath”, aby uzyskać pierwszy parametr.
akauppi
1
Ten jest bardziej klasy przemysłowej (ze względu na wyraźny wybór CharSets), ale brakuje mu prostoty (i jednej wkładki ...) reflect.io.File.writeAll(contents). Czemu? Trzy wiersze w przypadku dołączenia dwóch instrukcji importu. Może IDE robi to automatycznie, ale jeśli w REPL nie jest to takie proste.
javadba
3
@javadba jesteśmy w JVM, importy prawie nie liczą się jako „linie”, zwłaszcza, że ​​alternatywą jest prawie zawsze dodanie nowej zależności od biblioteki. W każdym razie Files.writeprzyjmuje również java.lang.Iterablejako drugi argument i możemy go uzyskać ze Scali Iterable, czyli prawie dowolnego typu kolekcji, używając konwertera: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

Oto zwięzły, jednowierszowy tekst z użyciem pliku Reflect.io., który działa ze Scala 2.12:

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

Alternatywnie, jeśli chcesz użyć bibliotek Java, możesz zrobić to hack:

Some(new PrintWriter("filename")).foreach{p => p.write("hello world"); p.close}
Garrett Hall
źródło
1
+1 Działa świetnie. Some/ foreachKombi jest nieco funky, ale chodzi o rzecz bez try / catch / finally.
Brent Faust
3
Cóż, jeśli zapis zgłasza wyjątek, możesz chcieć zamknąć plik, jeśli planujesz odzyskać z wyjątku i ponownie odczytać / zapisać plik. Na szczęście scala zapewnia również jednoliniowe wskazówki.
Garrett Hall
25
Nie jest to zalecane, ponieważ pakiet scala.tools.nsc.io nie jest częścią publicznego interfejsu API, ale jest używany przez kompilator.
Giovanni Botta
3
Some/ foreachHack jest dokładnie, dlaczego wielu ludzi nienawidzi Scala dla nieczytelnym kodem to powoduje hakerów do produktów.
Erik Kaplun
3
scala.tootls.nsc.io.Filejest aliasem dla reflect.io.File. Wciąż wewnętrzny interfejs API, ale przynajmniej trochę krótszy.
kostja
41

Jeśli podoba Ci się składnia Groovy, możesz użyć wzorca projektowego Pimp-My-Library, aby przenieść ją do 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 )

}

Będzie działać zgodnie z oczekiwaniami:

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
paradygmatyczny
źródło
2
Nigdy nie wywołujesz close w zwróconym wystąpieniu Source.fromFile, co oznacza, że ​​zasób nie zostanie zamknięty, dopóki nie zostanie GCed (garbage Collected). Twój PrintWriter nie jest buforowany, więc używa małego domyślnego bufora JVM o wielkości 8 bajtów, potencjalnie znacznie spowalniając twoje IO. Właśnie utworzyłem odpowiedź w podobnym wątku, który dotyczy tych problemów: stackoverflow.com/a/34277491/501113
chaotic3quilibrium
1
Masz rację. Ale rozwiązanie, które tutaj podałem, działa dobrze w programach krótkotrwałych z małymi IO. Nie polecam go do przetwarzania na serwerze lub dużych danych (z reguły ponad 500 MB).
paradygmatyczny
23
import sys.process._
"echo hello world" #> new java.io.File("/tmp/example.txt") !
xiefei
źródło
Nie działa dla mnie w REPL. Brak komunikatu o błędzie, ale jeśli spojrzę na /tmp/example.txt, nie ma.
użytkownik nieznany
@user nieznany, Przepraszamy za pominięcie znaku „!” na końcu polecenia, naprawione teraz.
xiefei
Wspaniale! Teraz, gdy to działa, chciałbym wiedzieć, dlaczego. Co to jest #>, co to !robi?
użytkownik nieznany
10
To rozwiązanie nie jest przenośne! działa tylko w systemach * nix.
Giovanni Botta,
2
To nie zadziała w przypadku pisania dowolnych ciągów. Będzie działać tylko dla krótkich ciągów, które można przekazać jako argumenty do echonarzędzia wiersza poleceń.
Rich
15

Mikro biblioteka, którą napisałem: https://github.com/pathikrit/better-files

file.write("Hi!")

lub

file << "Hi!"
pathikrit
źródło
3
Pokochaj swoją bibliotekę! To pytanie jest jednym z największych hitów podczas wyszukiwania w Google, jak napisać plik za pomocą scali - teraz, gdy Twój projekt urósł, możesz chcieć nieco rozszerzyć swoją odpowiedź?
asac
12

Możesz łatwo użyć Apache File Utils . Spójrz na funkcję writeStringToFile. Używamy tej biblioteki w naszych projektach.

RyuuGan
źródło
3
Ja też go używam cały czas. Jeśli uważnie przeczytasz pytanie, już wiem, dlaczego nie chcę korzystać z biblioteki.
smartnut007
Bez korzystania z biblioteki stworzyłem rozwiązanie, które obsługuje wyjątki podczas odczytu / zapisu ORAZ może być buforowane poza niewielkimi domyślnymi wartościami bufora zapewnianymi przez biblioteki Java: stackoverflow.com/a/34277491/501113
chaotic3quilibrium
7

Jeden ma również ten format, który jest zwięzły, a podstawowa biblioteka jest pięknie napisana (patrz kod źródłowy):

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
źródło
7

Myślę, że jest to wystarczająco zwięzłe:

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
źródło
4
W porządku, ale to „wystarczająco zwięzłe” nie klasyfikuje się jako „jedno stwierdzenie”: P
Erik Kaplun,
Ten kod rozwiązuje wiele problemów z językiem Java. Niestety Scala nie uważa IO za wystarczająco ważne, więc biblioteka standardowa go nie zawiera.
Chris,
Odpowiedź ukrywa problemy z osieroconym zasobem w nowym FileWriter. Jeśli nowy FileWriter powiedzie się, ale nowy BufferedWriter nie powiedzie się, wystąpienie FileWriter nigdy nie jest ponownie widoczne i pozostaje otwarte do GCed (Garbage Collected) i może nie zostać zamknięte nawet wtedy (ze względu na sposób finalizacji gwarancji pracy w JVM). Napisałem odpowiedź na podobne pytanie, które rozwiązuje te problemy: stackoverflow.com/a/34277491/501113
chaotic3quilibrium
2

Możesz to zrobić za pomocą mieszanki bibliotek Java i Scala. Będziesz mieć pełną kontrolę nad kodowaniem znaków. Niestety, uchwyty plików nie zostaną poprawnie zamknięte.

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
źródło
Masz w kodzie problem z osieroconym wystąpieniem zasobu. Ponieważ nie przechwytujesz wystąpień przed wywołaniem, jeśli albo zgłosi wyjątek, zanim metoda, którą wywołujesz, zostanie przekazana parametrów, zasoby, których wystąpienie zostało pomyślnie utworzone, nie będą zawsze zamknięte, aż do GCed (Garbage Collected), a nawet to może nie być spowodowane sposobem działania gwarancji GC. Napisałem odpowiedź na podobne pytanie, które dotyczy tych problemów: stackoverflow.com/a/34277491/501113
chaotic3quilibrium
1
Masz rację, a Twoje rozwiązanie jest całkiem niezłe. Ale tutaj wymaganiem było zrobienie tego w jednej linii. A kiedy uważnie czytasz, wspomniałem o wycieku zasobów w mojej odpowiedzi jako ograniczeniu, które wiąże się z tym wymaganiem i moim podejściem. Twoje rozwiązanie jest fajne, ale nie spełniałoby tego wymagania jednej linii.
stefan.schwetschke
2

Wiem, że to nie jest jedna linijka, ale o ile wiem, rozwiązuje ona kwestie bezpieczeństwa;

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

Jeśli nie dbałeś o obsługę wyjątków, możesz napisać

writer.map(w => {w.writer("data"); w}).recoverWith{case _ => writer}.map(_.close)
J_mie6
źródło
1

AKTUALIZACJA: od tego czasu stworzyłem bardziej efektywne rozwiązanie, nad którym omówiłem tutaj: https://stackoverflow.com/a/34277491/501113

Coraz częściej pracuję nad arkuszem Scala w środowisku Scala IDE dla Eclipse (i uważam, że w IntelliJ IDEA jest coś równoważnego). W każdym razie muszę być w stanie wykonać jedną linijkę, aby wydrukować część zawartości, gdy otrzymuję komunikat „Wyjście przekracza granicę odcięcia”. wiadomość, jeśli robię coś ważnego, szczególnie z kolekcjami Scala.

Wymyśliłem jedną linijkę, którą wstawiam na górze każdego nowego arkusza Scala, aby to uprościć (i dlatego nie muszę wykonywać całego ćwiczenia importowania biblioteki zewnętrznej z bardzo prostej potrzeby). Jeśli jesteś sticklerem i zauważysz, że technicznie jest to dwie linie, to tylko po to, aby uczynić ją bardziej czytelną na tym forum. To jest pojedyncza linia w moim arkuszu 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}}

A użycie jest po prostu:

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

Dzięki temu mogę opcjonalnie podać nazwę pliku, jeśli chcę mieć dodatkowe pliki poza domyślnymi (co całkowicie zastępuje plik za każdym razem, gdy wywoływana jest metoda).

Tak więc drugie użycie to po prostu:

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

Cieszyć się!

chaotic3quilibrium
źródło
1

Użyj biblioteki ammonite ops . Składnia jest bardzo minimalna, ale rozpiętość biblioteki jest prawie tak szeroka, jak można by się spodziewać po wykonaniu takiego zadania w języku skryptowym powłoki, takim jak bash.

Na stronie, do której się łącze, pokazuje wiele operacji, które można wykonać z biblioteką, ale aby odpowiedzieć na to pytanie, oto przykład zapisu do pliku

import ammonite.ops._
write(pwd/'"file.txt", "file contents")
smac89
źródło
-1

Dzięki magii średnika możesz zrobić wszystko, co ci się podoba, w jednej linii.

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
źródło
Nie ma tu argumentu. Postanowiłem nie zapamiętywać, które interfejsy API wymagają ode mnie spłukiwania części mojego życia, więc zawsze to robię.
Ion Freeman