Чтение и запись в файл в 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