在Haskell中读写文件

Afo*_*tos 8 io haskell file

我试图读取文件的内容,将文本转为大写,然后将其写回.

这是我写的代码:

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 ()
Run Code Online (Sandbox Code Playgroud)

但是,这对文件没有任何写入,事实上,它甚至可以清除它.

我做了一些改变:

main = do
    handle <- openFile "file.txt" ReadWriteMode
    contents <- hGetContents handle
    writeFile "file.txt" (map toUpper contents)
    hClose handle
    return ()
Run Code Online (Sandbox Code Playgroud)

但是,我得到了错误resource busy (file is locked).我怎样才能使这个工作以及为什么它在两种情况下都不起作用?

bhe*_*ilr 8

懒惰的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
Run Code Online (Sandbox Code Playgroud)

我会说这个代码实际上更好,因为你不会回写一个已经为空的文件,这是一个毫无意义的操作.

另一种方法是使用流媒体库,这pipes是一个流行的选择,有一些很好的教程和坚实的数学基础,这也是我的选择.


bwr*_*oga 6

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

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

在第二个示例中,您打开文件,然后尝试写入文件,但是因为直到需要(转换和写回时)才真正读取内容,因此您最终尝试写入和读取相同的内容同时存档。

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

  • 无论如何,写入到新文件并重命名的方法实际上更好,因为它不会在写入过程中崩溃的情况下丢失数据。 (2认同)

ram*_*ion 5

这里发生了一些事情

您打开了文件ReadWriteMode,但只读取了内容。为什么不为两者使用相同的手柄呢?

main = do
    handle <- openFile "file.txt" ReadWriteMode
    contents <- hGetContents' handle
    hSeek handle AbsoluteSeek 0
    hPutStr handle (map toUpper contents)
    hClose handle
    return ()
Run Code Online (Sandbox Code Playgroud)

hGetContents会将句柄置于半关闭状态,因此您需要其他东西来读取文件内容:

hGetContents' :: Handle -> IO String
hGetContents' h = do
  eof <- hIsEOF h
  if eof
    then
      return []
    else do
      c <- hGetChar h
      fmap (c:) $ hGetContents' h
Run Code Online (Sandbox Code Playgroud)


max*_*kin 5

@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
Run Code Online (Sandbox Code Playgroud)