2015-07-10 287 views
4

我正在尝试读取文件的内容,将文本转换为大写,然后将其写回。在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时,文件的内容不会立即读入。他们在需要时阅读。

在你的第一个例子中,你打开文件并说你想要内容,但是在你做任何事情之前关闭文件。然后你写入文件。当您写入现有文件时,内容将被清除,但是由于您关闭了文件,因此无法再访问文件内容。

在第二个示例中,您打开文件,然后尝试写入文件,但是因为内容在需要时才会真正读取(当它们被转换并写回时),最终会尝试写入并同时读取同一个文件。

你可以写一个名为FILE2.TXT然后当你完成文件,删除file.txt的和重命名FILE2.TXT到FILE.TXT

+0

你已经正确地指出了这个问题,但您的解决方案是不必要的复杂。 – leftaroundabout

+0

@leftaroundabout bheklilr的方法是什么,你会推荐? – bwroga

+0

是的,'readFile'是我推荐的,除非有理由不(性能等),或者你已经在项目中使用'conduit'或'pipes'。 – leftaroundabout

6

懒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是一个流行的选择,有一些很好的教程和坚实的数学基础,这也是我的选择。

+0

第二次尝试失败,因为OP试图打开一个文件进行写入,因为写入已经打开(因为'ReadWriteMode') –

+0

Anton Guryanov:在我的实验中,即使文件刚刚用ReadMode '。 – rampion

+0

你是对的,这很重要。 – leftaroundabout

3

有几件事情会在这里

您在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 
+1

请不要推荐kludges来让'Handle's工作,也没有提到一些更好的选择。 FWIW,'hGetContents''最好用[deepseq]实现(http://hackage.haskell.org/package/deepseq-1.4.1.1/docs/Control-DeepSeq.html#v:deepseq)(它实际上是在该页面上的示例)。 – leftaroundabout

+0

或只是'hGetContents'h = do {c < - hGetContents h;如果(长度c> 0)则返回c,否则返回“”}'。 –

+0

Will Ness:这并不妨碍把手处于半关闭状态 – rampion

4

@ 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