Scala: grava a string no arquivo em uma instrução

144

Para ler arquivos no Scala, há

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

Existe uma maneira equivalente e concisa de escrever uma string no arquivo?

A maioria dos idiomas suporta algo assim. O meu favorito é o Groovy:

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

Eu gostaria de usar o código para programas que variam de uma única linha a uma pequena página de código. Ter que usar sua própria biblioteca não faz sentido aqui. Espero que uma linguagem moderna me permita escrever algo em um arquivo convenientemente.

Existem posts semelhantes a este, mas eles não respondem à minha pergunta exata ou estão focados nas versões mais antigas do Scala.

Por exemplo:

smartnut007
fonte
Veja esta pergunta. Concordo com a resposta mais bem classificada - é melhor ter sua própria biblioteca pessoal.
ffriend 30/07
2
neste caso, não concordo que seja necessário escrever sua própria biblioteca pessoal. Normalmente, quando você está escrevendo pequenos programas para fazer coisas ad hoc (talvez eu queira apenas escrevê-lo como um script de escala de página única ou apenas no REPL). Então, acessar uma biblioteca pessoal se torna uma dor.
smartnut007
Basicamente, parece que não há nada no scala 2.9 para isso neste momento. Não sei como manter essa pergunta em aberto.
precisa saber é o seguinte
1
Se você procurar java.io no código fonte do Scala, não encontrará muitas ocorrências e muito menos para operações de saída, principalmente o PrintWriter. Portanto, até que a biblioteca Scala-IO se torne parte oficial do Scala, precisamos usar Java puro, como mostra a paradigmática.
PhiLho 29/08
sim, o prob também precisa de um scala-io compatível com as melhorias do jdk7 IO.
precisa saber é o seguinte

Respostas:

77

Uma linha concisa:

import java.io.PrintWriter
new PrintWriter("filename") { write("file contents"); close }
LRLucena
fonte
14
Embora essa abordagem pareça boa, ela não é segura contra exceção nem segura para codificação. Se ocorrer uma exceção write(), closenunca será chamado e o arquivo não será fechado. PrintWritertambém usa a codificação padrão do sistema, o que é muito ruim para a portabilidade. E, finalmente, essa abordagem gera uma classe separada especificamente para essa linha (no entanto, dado que o Scala já gera toneladas de classes, mesmo para um código simples, isso dificilmente é uma desvantagem).
Vladimir Matveev
Pelo comentário acima, embora seja uma linha única, não é seguro. Se você quiser mais segurança ao ter mais opções em torno de localização e / ou buffer de entrada, ver a resposta que eu só postou em um segmento semelhante: stackoverflow.com/a/34277491/501113
chaotic3quilibrium
2
Para depuração-logging temporária Eu useinew PrintWriter(tmpFile) { try {write(debugmsg)} finally {close} }
Randall Whitman
Estilisticamente é provavelmente melhor para uso close(), eu acho
Casey
São duas linhas e podem ser facilmente transformadas em uma linha como esta:new java.io.PrintWriter("/tmp/hello") { write("file contents"); close }
Philluminati
163

É estranho que ninguém tenha sugerido operações NIO.2 (disponíveis desde o 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))

Eu acho que essa é de longe a maneira mais simples, fácil e idiomática, e não precisa de nenhuma dependência do próprio Java.

Vladimir Matveev
fonte
Isso ignora o caractere de nova linha por algum motivo.
Kakaji 12/03/16
@Kakaji, você poderia por favor elaborar? Acabei de testar com strings com novas linhas e funciona perfeitamente. Ele simplesmente não pode filtrar nada - Files.write () grava a matriz de bytes como um blob, sem analisar seu conteúdo. Afinal, em alguns dados binários 0x0d byte pode ter um significado importante além da nova linha.
Vladimir Matveev
4
Nota adicional: se você possui um arquivo, basta '.toPath' para obter o primeiro parâmetro.
akauppi
1
Este é mais de nível industrial (devido à opção explícita de CharSets), mas não possui a simplicidade (e um liner ..) do reflect.io.File.writeAll(contents). Por quê? Três linhas quando você inclui as duas instruções de importação. Talvez o IDE faça isso automaticamente, mas se no REPL não for tão fácil.
Javadba
3
@javadba estamos na JVM, as importações praticamente não contam como 'linhas', especialmente porque a alternativa está quase sempre adicionando uma nova dependência de biblioteca. De qualquer forma, Files.writetambém aceita um java.lang.Iterablecomo o segundo argumento, e podemos obter isso de um Scala Iterable, ou seja, praticamente qualquer tipo de coleção, usando um conversor: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

Aqui está um alinhamento conciso usando o reflect.io.file, que funciona com o Scala 2.12:

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

Como alternativa, se você quiser usar as bibliotecas Java, poderá fazer isso:

Some(new PrintWriter("filename")).foreach{p => p.write("hello world"); p.close}
Garrett Hall
fonte
1
+1 Funciona muito bem. O Some/ foreachcombo é um pouco descolado, mas vem com o benefício de não tentar / pegar / finalmente.
Brent Faust
3
Bem, se a gravação lança uma exceção, você pode fechar o arquivo se planeja se recuperar da exceção e ler / gravar o arquivo novamente. Felizmente, a scala fornece uma linha para fazer isso também.
Garrett Hall
25
Isso não é recomendado, pois o pacote scala.tools.nsc.io não faz parte da API pública, mas é usado pelo compilador.
Giovanni Botta
3
O Some/ foreachhack é exatamente por que muitas pessoas odeiam o Scala pelo código ilegível que ele faz com que os hackers produzam.
Erik Kaplun
3
scala.tootls.nsc.io.Fileé um alias para o reflect.io.File. API ainda interna, mas pelo menos um pouco menor.
22415 kostja
41

Se você gosta da sintaxe Groovy, pode usar o padrão de design do Pimp-My-Library para trazê-lo para o 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á conforme o esperado:

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
paradigmático
fonte
2
Você nunca chama close na instância retornada Source.fromFile, o que significa que o recurso não é fechado até ser GCed (Garbage Collected). E o seu PrintWriter não é armazenado em buffer; portanto, ele está usando o pequeno buffer padrão da JVM de 8 bytes, potencialmente diminuindo significativamente a IO. I acabou de criar uma resposta em um segmento similar que lida com estas questões: stackoverflow.com/a/34277491/501113
chaotic3quilibrium
1
Você está certo. Mas a solução que dei aqui funciona bem para programas de curta duração com E / S pequenas. Eu não o recomendo para processos de servidor ou dados grandes (como regra geral, mais de 500 MB).
paradigmático
23
import sys.process._
"echo hello world" #> new java.io.File("/tmp/example.txt") !
xiefei
fonte
Não funciona para mim no REPL. Nenhuma mensagem de erro, mas se eu olhar para /tmp/example.txt, não há.
usuário desconhecido
@ usuário desconhecido, desculpe por perder o '!' no final do comando, corrigido agora.
Xiefei
Maravilhoso! Agora que funciona, eu gostaria de saber o porquê. O que é #>, o que !faz?
usuário desconhecido
10
Esta solução não é portátil! funciona apenas em sistemas * nix.
Giovanni Botta
2
Isso não funcionará para escrever seqüências arbitrárias. Ele funcionará apenas para cadeias curtas que você pode passar como argumentos para a echoferramenta de linha de comando .
Rich
15

Uma micro biblioteca que escrevi: https://github.com/pathikrit/better-files

file.write("Hi!")

ou

file << "Hi!"
pathikrit
fonte
3
Ame sua biblioteca! Essa pergunta é um dos principais hits ao pesquisar no Google como escrever um arquivo com o scala - agora que seu projeto ficou maior, você pode expandir um pouco sua resposta?
ASAC
12

Você pode usar facilmente o Apache File Utils . Veja a função writeStringToFile. Usamos essa biblioteca em nossos projetos.

RyuuGan
fonte
3
Eu também uso o tempo todo. Se você ler a pergunta com atenção, já mencionei por que não quero usar uma biblioteca.
226126 phpBaixar
Sem o uso de uma biblioteca, eu criei uma solução que lida com exceções durante a leitura / escreve e é capaz de ser tamponado além dos minúsculos padrões tampão fornecidas pelas bibliotecas Java: stackoverflow.com/a/34277491/501113
chaotic3quilibrium
7

Também se tem esse formato, que é conciso e a biblioteca subjacente é lindamente escrita (consulte o código-fonte):

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

Isso é conciso o suficiente, eu acho:

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
fonte
4
Bastante justo, mas este "bastante conciso" não classifica como "uma declaração": P
Erik Kaplun
Esse código elimina muitos dos problemas percebidos do Java. Infelizmente, Scala não considera IO importante o suficiente para que a biblioteca padrão não inclua uma.
Chris
A resposta oculta um problema de recurso órfão com o novo FileWriter. Se o novo FileWriter for bem-sucedido, mas o novo BufferedWriter falhar, a instância do FileWriter nunca será vista novamente e permanecerá aberta até GCed (Garbage Collected) e poderá não ser fechada mesmo assim (devido à maneira como as garantias de finalização funcionam na JVM). Eu escrevi uma resposta a uma pergunta semelhante, que aborda estas questões: stackoverflow.com/a/34277491/501113
chaotic3quilibrium
2

Você pode fazer isso com uma mistura de bibliotecas Java e Scala. Você terá controle total sobre a codificação de caracteres. Mas, infelizmente, os identificadores de arquivo não serão fechados corretamente.

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
fonte
Você tem um problema de instância de recurso órfão no seu código. Como você não está capturando as instâncias anteriores à sua chamada, se uma lança antes que o método que você está chamando tenha passado os parâmetros, os recursos que foram instanciados com êxito não serão fechados até o GCed (Garbage Collected) e até talvez não seja devido à maneira como o GC garante o trabalho. Eu escrevi uma resposta a uma pergunta semelhante, que aborda estas questões: stackoverflow.com/a/34277491/501113
chaotic3quilibrium
1
Você está certo e sua solução é bastante agradável. Mas aqui o requisito era fazê-lo em uma linha. E quando você lê com atenção, mencionei o vazamento de recursos na minha resposta como um limite que acompanha esse requisito e a minha abordagem. Sua solução é boa, mas não corresponderia a esse requisito de uma linha.
stefan.schwetschke
2

Eu sei que não é uma linha, mas resolve os problemas de segurança, tanto quanto posso dizer;

// 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 você não se importou com o tratamento de exceções, pode escrever

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

ATUALIZAÇÃO: Desde então, criei uma solução mais eficaz sobre a qual elaborei aqui: https://stackoverflow.com/a/34277491/501113

Eu me pego trabalhando cada vez mais na Planilha Scala dentro do Scala IDE para Eclipse (e acredito que exista algo equivalente no IntelliJ IDEA). De qualquer forma, eu preciso ser capaz de criar uma única linha para produzir parte do conteúdo, pois obtenho o "Saída excede o limite de corte". mensagem se estou fazendo algo significativo, especialmente com as coleções Scala.

Eu vim com uma linha que eu insiro na parte superior de cada nova planilha Scala para simplificar isso (e, portanto, não preciso fazer todo o exercício de importação de biblioteca externa para uma necessidade muito simples). Se você é um defensor e percebe que são tecnicamente duas linhas, é apenas para torná-lo mais legível neste fórum. É uma única linha na minha planilha 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 o uso é simplesmente:

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

Isso permite que eu forneça opcionalmente o nome do arquivo, caso eu queira ter arquivos adicionais além do padrão (que substitui completamente o arquivo toda vez que o método é chamado).

Portanto, o segundo uso é simplesmente:

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

Aproveitar!

chaotic3quilibrium
fonte
1

Use a biblioteca de operações de amonite . A sintaxe é muito mínima, mas a amplitude da biblioteca é quase tão ampla quanto o que se esperaria de tentar uma tarefa dessas em uma linguagem de script de shell como o bash.

Na página a que vinculei, ele mostra inúmeras operações que podemos fazer com a biblioteca, mas para responder a essa pergunta, este é um exemplo de gravação em um arquivo

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

Com a magia do ponto e vírgula, você pode fazer o que quiser como uma linha.

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
fonte
Nenhum argumento aqui. Decidi não lembrar quais APIs precisam que eu liberasse parte da minha vida, então sempre o faço.
Ion Freeman