Чтение и запись в файл в Haskell
Я пытаюсь прочитать содержимое файла, повернуть текст в верхний регистр, а затем записать его обратно.
вот код, который я написал:
import System.IO
import Data.Char
main = do
    handle <- openFile "file.txt" ReadWriteMode
    contents <- hGetContents handle
    hClose handle
    writeFile "file.txt" (map toUpper contents)
    return ()
однако это ничего не записывает в файл, на самом деле, он даже очищает его.
я внес некоторые изменения:
main = do
    handle <- openFile "file.txt" ReadWriteMode
    contents <- hGetContents handle
    writeFile "file.txt" (map toUpper contents)
    hClose handle
    return ()
тем не менее, я получаю ошибку resource busy (file is locked). Как я могу заставить это работать и почему это не сработало в обоих случаях?
4 ответов
Я думаю, ваша проблема в том, что hgetcontents ленив. Содержимое файла не сразу считывается при использовании hGetContents. Их читают, когда они нужны.
в первом примере вы открываете файл и говорите, что хотите его содержимое, но затем закрываете файл, прежде чем что-либо с ним делать. Затем вы пишете в файл. При записи в существующий файл содержимое удаляются, но после закрытия файла у вас больше нет доступа к файлу содержание.
во втором примере вы открываете файл, а затем пытаетесь записать его в файл, но поскольку содержимое не читается до тех пор, пока оно не понадобится (когда оно преобразуется и записывается обратно), вы в конечном итоге пытаетесь писать и читать из одного файла одновременно.
вы можете записать в файл с именем файл2.тхт затем, когда вы закончите, удалите файл.txt и переименовать файл2.txt в файл.txt
ленивый IO плох, и это обычно считается болевой точкой в Haskell.  В основном contents не оценивается, пока вы не вернете его на диск, и в этот момент он не может быть оценен, потому что файл уже закрыт.  Вы можете исправить это несколькими способами, не прибегая к дополнительным библиотекам, которые вы можете использовать readFile функция, а затем проверьте длину, прежде чем писать обратно:
import Control.Monad (when)
main = do
    contents <- readFile "file.txt"
    let newContents = map toUpper contents
    when (length newContents > 0) $
        writeFile "file.txt" newContents
Я бы сказал, что этот код на самом деле лучше, потому что вы не пишете вернуться к файлу, который уже пуст, бессмысленная операция.
другой способ-использовать потоковую библиотеку,pipes является популярным выбором с некоторыми хорошими учебниками и прочной математической основой, и это был бы мой выбор.
@bwroga ответ совершенно правильный. Вот реализация предлагаемого подхода (запись во временный файл и переименование):
import Data.Char (toUpper)
import System.Directory (renameFile, getTemporaryDirectory)
import System.Environment (getArgs)
main = do
    [file] <- getArgs
    tmpDir <- getTemporaryDirectory
    let tmpFile = tmpDir ++ "/" ++ file
    readFile file >>= writeFile tmpFile . map toUpper
    renameFile tmpFile file
здесь происходит пара вещей
вы открыли файл в ReadWriteMode, но только читать содержимое. Почему бы не использовать одну и ту же ручку для обоих?
main = do
    handle <- openFile "file.txt" ReadWriteMode
    contents <- hGetContents' handle
    hSeek handle AbsoluteSeek 0
    hPutStr handle (map toUpper contents)
    hClose handle
    return ()
hGetContents поместит ручку в полузакрытое состояние, поэтому вам понадобится что-то еще, чтобы прочитать содержимое файла:
hGetContents' :: Handle -> IO String
hGetContents' h = do
  eof <- hIsEOF h
  if eof
    then
      return []
    else do
      c <- hGetChar h
      fmap (c:) $ hGetContents' h
