Scala: записать строку в файл одним оператором

147

Для чтения файлов в Scala есть

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

Есть ли эквивалентный и краткий способ записать строку в файл?

Большинство языков поддерживают что-то подобное. Мне больше всего нравится Groovy:

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

Я хотел бы использовать код для программ от одной строки до короткой страницы кода. Использование собственной библиотеки здесь не имеет смысла. Я ожидаю, что современный язык позволит мне удобно записывать что-то в файл.

Есть сообщения, похожие на это, но они не отвечают на мой точный вопрос или сосредоточены на более старых версиях Scala.

Например:

smartnut007
источник
См. Этот вопрос. Я согласен с самым высоким ответом - лучше иметь свою личную библиотеку.
ffriend
2
в этом случае я не согласен с тем, что нужно писать свою личную библиотеку. Обычно, когда вы пишете небольшие программы для выполнения специальных задач (возможно, я просто хочу написать ее как одностраничный скрипт scala или просто в REPL). Тогда доступ к личной библиотеке становится проблемой.
smartnut007
По сути, похоже, что на данный момент в scala 2.9 для этого нет ничего. Не знаю, как оставить этот вопрос открытым.
smartnut007 01
1
Если вы выполните поиск java.io в исходном коде Scala, вы не найдете много вхождений, и еще меньше для операций вывода, особенно для PrintWriter. Итак, пока библиотека Scala-IO не станет официальной частью Scala, мы должны использовать чистую Java, как показано парадигматикой.
PhiLho
да, вероятно, также нужен scala-io, совместимый с улучшениями ввода-вывода jdk7.
smartnut007

Ответы:

81

Краткая одна строка:

import java.io.PrintWriter
new PrintWriter("filename") { write("file contents"); close }
LRLucena
источник
14
Хотя этот подход выглядит хорошо, он не безопасен ни в отношении исключений, ни в отношении кодирования. Если происходит исключение write(), closeникогда не будет вызываться, и файл не будет закрыт. PrintWriterтакже использует системную кодировку по умолчанию, что очень плохо для переносимости. И, наконец, этот подход генерирует отдельный класс специально для этой строки (однако, учитывая, что Scala уже генерирует тонны классов даже для простого кода, это вряд ли само по себе является недостатком).
Владимир Матвеев
Согласно комментарию выше, хотя это однострочный, это небезопасно. Если вы хотите большей безопасности, имея больше возможностей для определения местоположения и / или буферизации ввода, см. Ответ, который я только что опубликовал в аналогичной ветке: stackoverflow.com/a/34277491/501113
chaotic3quilibrium
2
Для временного ведения журнала отладки я использовалnew PrintWriter(tmpFile) { try {write(debugmsg)} finally {close} }
Randall Whitman
Стилистически, наверное, лучше использовать close(),
Кейси
1
Это две строчки, и их легко превратить в одну, вот так:new java.io.PrintWriter("/tmp/hello") { write("file contents"); close }
Филлуминати,
167

Странно, что никто не предлагал операции NIO.2 (доступны с 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))

Я думаю, что это, безусловно, самый простой, самый легкий и самый идиоматический способ, и он не требует никаких зависимостей без самой Java.

Владимир Матвеев
источник
По какой-то причине это игнорирует символ новой строки.
Kakaji
@ Какадзи, не могли бы вы уточнить? Я только что протестировал его со строками с новой строкой, и он отлично работает. Он просто не может ничего фильтровать - Files.write () записывает массив байтов как blob, не анализируя его содержимое. В конце концов, в некоторых двоичных данных байт 0x0d может иметь важное значение, отличное от новой строки.
Владимир Матвеев
4
Дополнительное примечание: если у вас есть файл, просто «.toPath», чтобы получить первый параметр.
akauppi
1
Это более промышленный уровень (из-за явного выбора CharSets), но ему не хватает простоты (и одного лайнера ...), чем у reflect.io.File.writeAll(contents). Зачем? Три строки, когда вы включаете два оператора импорта. Возможно, IDE сделает это за вас автоматически, но в REPL это не так просто.
javadba
3
@javadba, мы находимся в JVM, импорт практически не считается «строками», особенно потому, что альтернативой почти всегда является добавление новой зависимости библиотеки. Во всяком случае, Files.writeтакже принимает в java.lang.Iterableкачестве второго аргумента, и мы можем получить его из Scala Iterable, то есть практически любого типа коллекции, используя конвертер: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

Вот краткий однострочный файл с использованием файла reflection.io, он работает в Scala 2.12:

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

В качестве альтернативы, если вы хотите использовать библиотеки Java, вы можете сделать это:

Some(new PrintWriter("filename")).foreach{p => p.write("hello world"); p.close}
Гаррет Холл
источник
1
+1 Отлично работает. Some/ foreachКомбо немного напуганный, но это приходит с выгодой не TRY / улова / наконец.
Brent Faust
3
Что ж, если запись вызывает исключение, вы можете закрыть файл, если вы планируете восстановиться после исключения и снова прочитать / записать файл. К счастью, scala предоставляет однострочники и для этого.
Garrett Hall
25
Это не рекомендуется, поскольку пакет scala.tools.nsc.io не является частью общедоступного API, но используется компилятором.
Джованни Ботта,
3
Именно благодаря Some/ foreachhack многие люди ненавидят Scala за нечитаемый код, который он заставляет создавать хакеры.
Эрик Каплун
3
scala.tootls.nsc.io.Fileэто псевдоним для reflect.io.File. По-прежнему внутренний API, но хоть немного короче.
Костя
41

Если вам нравится синтаксис Groovy, вы можете использовать шаблон проектирования Pimp-My-Library, чтобы перенести его в 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 )

}

Он будет работать как положено:

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
Вы никогда не вызываете close для возвращенного экземпляра Source.fromFile, что означает, что ресурс не закрывается, пока он не будет GCed (сбор мусора). И ваш PrintWriter не буферизуется, поэтому он использует крошечный буфер JVM по умолчанию размером 8 байтов, что потенциально значительно замедляет ваш ввод-вывод. Я только что создал ответ в аналогичной ветке, посвященной этим проблемам: stackoverflow.com/a/34277491/501113
chaotic3quilibrium
1
Ты прав. Но решение, которое я здесь дал, хорошо работает для недолговечных программ с небольшим объемом ввода-вывода. Я не рекомендую его для серверного процесса или больших данных (как правило, более 500 МБ).
парадигматический
22
import sys.process._
"echo hello world" #> new java.io.File("/tmp/example.txt") !
xiefei
источник
У меня не работает в REPL. Нет сообщения об ошибке, но если я посмотрю на /tmp/example.txt, его нет.
user unknown
@user unknown, извините за отсутствие символа '!' в конце команды, исправлено сейчас.
xiefei
Замечательно! Теперь, когда это работает, я хотел бы знать, почему. Что есть #>, что делает !?
user unknown
10
Это решение не переносимо! работает только в системах * nix.
Джованни Ботта
2
Это не сработает для записи произвольных строк. Это будет работать только для коротких строк, которые вы можете передать в качестве аргументов echoинструменту командной строки .
Rich
16

Я написал микробиблиотеку : https://github.com/pathikrit/better-files

file.write("Hi!")

или

file << "Hi!"
патикрит
источник
3
Любите свою библиотеку! Этот вопрос является одним из самых популярных при поиске в Google того, как написать файл с помощью scala - теперь, когда ваш проект стал больше, вы можете немного расширить свой ответ?
asachet 06
12

Вы можете легко использовать утилиты Apache File Utils . Посмотрите на функцию writeStringToFile. Мы используем эту библиотеку в наших проектах.

RyuuGan
источник
3
Я тоже постоянно им пользуюсь. Если вы внимательно прочитали вопрос, я уже понял, почему я не хочу использовать библиотеку.
smartnut007
Без использования библиотеки я создал решение, которое обрабатывает исключения во время чтения / записи и может буферизоваться за пределами крошечных значений буфера по умолчанию, предоставляемых библиотеками Java: stackoverflow.com/a/34277491/501113
chaotic3quilibrium
7

У одного также есть этот формат, который одновременно краток, а основная библиотека красиво написана (см. Исходный код):

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

Думаю, это достаточно лаконично:

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
Справедливо, но это «достаточно краткое» не классифицируется как «одно утверждение»: P
Erik Kaplun
Этот код эптимизирует многие предполагаемые проблемы Java. К сожалению, Scala не считает ввод-вывод достаточно важным, поэтому стандартная библиотека не включает его.
Крис
Ответ скрывает проблемы с потерянными ресурсами с новым FileWriter. Если новый FileWriter завершается успешно, но новый BufferedWriter терпит неудачу, экземпляр FileWriter больше никогда не появляется и остается открытым до GCed (сбор мусора) и может не быть закрыт даже тогда (из-за того, как гарантии finalize работают в JVM). Я написал ответ на аналогичный вопрос, в котором рассматриваются эти проблемы: stackoverflow.com/a/34277491/501113
chaotic3quilibrium
2

Вы можете сделать это с помощью сочетания библиотек Java и Scala. У вас будет полный контроль над кодировкой символов. Но, к сожалению, дескрипторы файлов закрываются некорректно.

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
источник
У вас есть проблема с экземпляром потерянного ресурса в вашем коде. Поскольку вы не захватываете экземпляры до своего вызова, если любой из них генерирует исключение до того, как вызываемому методу были переданы параметры, ресурсы, которые успешно созданы, не будут закрыты до тех пор, пока не будет выполнен сборщик мусора (сбор мусора), и даже тогда это может быть не из-за того, как GC гарантирует работу. Я написал ответ на аналогичный вопрос, который касается этих проблем: stackoverflow.com/a/34277491/501113
chaotic3quilibrium
1
Вы правы, и ваше решение довольно хорошее. Но здесь требовалось сделать это в одну строку. И если вы внимательно прочитаете, я упомянул утечку ресурсов в своем ответе как ограничение, связанное с этим требованием и моим подходом. Ваше решение хорошее, но не соответствует этому требованию одной строки.
stefan.schwetschke
2

Я знаю, что это не одна строка, но, насколько я могу судить, она решает проблемы безопасности;

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

Если вас не заботила обработка исключений, вы можете написать

writer.map(w => {w.writer("data"); w}).recoverWith{case _ => writer}.map(_.close)
J_mie6
источник
2

Используйте аммонитовые операции библиотеку . Синтаксис очень минимален, но ширина библиотеки почти такая же, как и следовало бы ожидать от попытки выполнения такой задачи на языке сценариев оболочки, таком как bash.

На странице, на которую я указал, показаны многочисленные операции, которые можно выполнять с библиотекой, но чтобы ответить на этот вопрос, это пример записи в файл.

import ammonite.ops._
write(pwd/'"file.txt", "file contents")
smac89
источник
Это сработало только для меня: write(pwd/"version", version)no'
pme
1

ОБНОВЛЕНИЕ: с тех пор я создал более эффективное решение, которое я подробно изложил здесь: https://stackoverflow.com/a/34277491/501113

Я все больше и больше работаю над Scala Worksheet в Scala IDE для Eclipse (и я считаю, что в IntelliJ IDEA есть что-то эквивалентное). В любом случае, мне нужно сделать однострочный вывод для вывода некоторого содержимого, поскольку я получаю сообщение «Вывод превышает предел отсечения». сообщение, если я делаю что-нибудь существенное, особенно с коллекциями Scala.

Я придумал однострочник, который вставляю в верхнюю часть каждого нового рабочего листа Scala, чтобы упростить это (и поэтому мне не нужно выполнять все упражнение по импорту внешней библиотеки для очень простой необходимости). Если вы приверженец и заметили, что технически это две строки, это только для того, чтобы сделать его более читаемым на этом форуме. Это одна строка в моем листе 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}}

И использование просто:

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

Это позволяет мне при желании указать имя файла, если я хочу иметь дополнительные файлы сверх значения по умолчанию (которое полностью перезаписывает файл при каждом вызове метода).

Итак, второе использование просто:

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

Наслаждайтесь!

хаотическое3равновесие
источник
-1

Благодаря магии точки с запятой вы можете сделать все, что угодно, однострочным.

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()
Ион Фриман
источник
Здесь нет аргументов. Я решил не вспоминать, какие API нужны мне, чтобы очистить часть моей жизни, поэтому я просто всегда это делаю.
Ион Фриман