2015-07-10 151 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