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