Haskell仍然很新......
我想读取文件的内容,用它做一些可能涉及IO的事情(现在使用putStrLn),然后将新内容写入同一个文件。
我想出了:
doit :: String -> IO ()
doit file = do
contents <- withFile tagfile ReadMode $ \h -> hGetContents h
putStrLn contents
withFile tagfile WriteMode $ \h -> hPutStrLn h "new content"
然而,由于懒惰,这不起作用。文件内容不会打印。我发现this post可以很好地解释它。
建议的解决方案是在putStrLn
中包含withFile
:
doit :: String -> IO ()
doit file = do
withFile tagfile ReadMode $ \h -> do
contents <- hGetContents h
putStrLn contents
withFile tagfile WriteMode $ \h -> hPutStrLn h "new content"
这有效,但这不是我想做的。 I中的操作最终会替换putStrLn
可能很长,我不想让文件保持打开状态。一般来说,我只希望能够获取文件内容,然后在使用该内容之前将其关闭。
我想出的解决方案如下:
doit :: String -> IO ()
doit file = do
c <- newIORef ""
withFile tagfile ReadMode $ \h -> do
a <- hGetContents h
writeIORef c $! a
d <- readIORef c
putStrLn d
withFile tagfile WriteMode $ \h -> hPutStrLn h "Test"
然而,我觉得这很长,有点混淆。我认为我不需要IORef
只是为了得到一个值,但我需要“放置”来放置文件内容。此外,如果没有$!
的严格性注释writeIORef
,它仍然无效。我猜IORef
本质上并不严格?
有人可以推荐一种更好,更短的方法来保持我想要的语义吗?
谢谢!
答案 0 :(得分:21)
第一个程序不起作用的原因是withFile
在执行传递给它的IO操作后关闭文件。在您的情况下,IO操作为hGetContents
,不立即读取文件,但仅在需要其内容时才执行。当您尝试打印文件的内容时,withFile
已经关闭了文件,因此读取失败(无声)。
您可以通过不重新发明轮子并仅使用readFile
和writeFile
修复此问题:
doit file = do
contents <- readFile file
putStrLn contents
writeFile file "new content"
但是假设您希望新内容依赖于旧内容。那么你通常不能简单地做
doit file = do
contents <- readFile file
writeFile file $ process contents
因为writeFile
可能影响readFile
返回的内容(请记住,它实际上还没有读取文件)。或者,根据您的操作系统,您可能无法在两个单独的句柄上打开相同的文件进行读写。简单但丑陋的解决方法是
doit file = do
contents <- readFile file
length contents `seq` (writeFile file $ process contents)
会强制readFile
读取整个文件并在writeFile
操作开始之前将其关闭。
答案 1 :(得分:10)
我认为解决此问题的最简单方法是使用严格的IO:
import qualified System.IO.Strict as S
main = do
file <- S.readFile "filename"
writeFile "filename" file
答案 2 :(得分:1)
您可以复制文件句柄,使用原始文件进行惰性写入(到文件末尾),然后用另一个进行惰性读取。因此,在附加到文件的情况下不会涉及严格注释。
import System.IO
import GHC.IO.Handle
main :: IO ()
main = do
h <- openFile "filename" ReadWriteMode
h2 <- hDuplicate h
hSeek h2 AbsoluteSeek 0
originalFileContents <- hGetContents h2
putStrLn originalFileContents
hSeek h SeekFromEnd 0
hPutStrLn h $ concatMap ("{new_contents}" ++) (lines originalFileContents)
hClose h2
hClose h
hDuplicate函数由GHC.IO.Handle模块提供。
返回原始句柄的副本,并带有自己的缓冲区。但是,两个Handles将共享一个文件指针。在复制句柄之前,刷新原始句柄的缓冲区,包括丢弃任何输入数据。
使用hSeek,您可以在读取或写入之前设置句柄的位置。
但我不确定使用&#34; AbsoluteSeek 0&#34;而不是&#34; SeekFromEnd 0&#34;用于写入,即覆盖内容。一般来说,我建议先写一个临时文件,例如使用openTempFile(来自System.IO),然后替换原文。
答案 3 :(得分:0)
这很难看,但你可以通过询问输入的length
来强制读取内容,并用你的do-block中的下一个语句seq
来读取它。但实际上解决方案是使用严格版本的hGetContents
。我不确定它叫什么。