【问题标题】:Read and writing to file in Haskell在 Haskell 中读取和写入文件
【发布时间】:2015-09-29 06:37:49
【问题描述】:

我正在尝试读取文件的内容,将文本转换为大写,然后将其写回。

这是我写的代码:

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)。我怎样才能让它工作以及为什么它在这两种情况下都不起作用?

【问题讨论】:

    标签: file haskell io


    【解决方案1】:

    Lazy 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 是一个受欢迎的选择,它有一些很好的教程和扎实的数学基础,这也是我的选择。

    【讨论】:

    • 而第二次尝试失败,因为OP试图打开一个文件进行写入,而该文件已经打开写入(因为ReadWriteMode)
    • Anton Guryanov:在我的实验中,即使文件刚刚用ReadMode 打开,它也失败了。
    • 通过在 newContents 上调用 length,您将强制在编写文件之前对 newContents 进行完全评估。对吗?
    • @bwroga length newContents 本身不会强制执行任何操作,只有当我们强制评估整个表达式 length newContents &gt; 0 时,newContents 才会被评估。
    • 因为第一个参数是在第二个之前评估的?
    【解决方案2】:

    我认为你的问题是 hGetContents 是懒惰的。使用 hGetContents 时不会立即读入文件的内容。它们会在需要时被读入。

    在您的第一个示例中,您打开文件并说您想要内容,然后在对它们进行任何操作之前关闭文件。然后你写入文件。当您写入现有文件时,内容会被清除,但由于您关闭了文件,您将无法再访问文件内容。

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

    您可以写入一个名为 file2.txt 的文件,然后在完成后删除 file.txt 并将 file2.txt 重命名为 file.txt

    【讨论】:

    • 你已经正确地指出了问题,但你的解决方案是不必要的复杂。
    • @leftaroundabout 你会推荐 bheklilr 的方法吗?
    • 是的,我推荐readFile,除非有理由不这样做(性能等),或者您已经在项目中使用conduit 或pipes。跨度>
    • write-to-new-file-and-rename 方法实际上更好,因为它不会在写入过程中发生崩溃的情况下丢失数据。
    【解决方案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
    

    【讨论】:

    • 请不要为了让Handles 工作而推荐kludges,也不要提及一些更好的选择。 FWIW,hGetContents' 最好用deepseq 实现(实际上就是那个页面上的例子)。
    • 或者只是hGetContents' h = do { c &lt;- hGetContents h ; if (length c &gt; 0) then return c else return "" }。
    • 威尔尼斯:这并不妨碍把手处于半封闭状态
    • 是的,但是返回的c 已经包含整个文件的内容,length 强制它一直到最后,所以没有什么可以从文件中读取的了。我用c &lt;- hGetContents' h; hClose h; ... use c... 对其进行了测试,它成功了。
    • Will Ness:虽然这是对 OP 问题的有效回答,但我的回答是关于使用单个句柄进行读写,所以我看不到相关性。
    【解决方案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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-04-16
      • 2012-11-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-06-01
      • 2014-03-20
      相关资源
      最近更新 更多