读取和写入 Haskell 中的文件
Read and writing to file in 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)
。我怎样才能让它工作,为什么它在这两种情况下都不起作用?
我认为你的问题是 hGetContents 是懒惰的。当您使用 hGetContents 时,文件的内容不会立即读入。他们会在需要时读入。
在您的第一个示例中,您打开文件并说您需要其中的内容,但在对它们执行任何操作之前关闭了文件。然后你写入文件。当您写入现有文件时,内容会被清除,但由于您关闭了文件,您将无法再访问文件内容。
在第二个示例中,您打开了文件,然后尝试写入文件,但是因为内容直到需要时才真正被读取(当它们被转换和写回时),您最终尝试写入到并同时读取同一个文件。
您可以写入名为 file2.txt 的文件,完成后删除 file.txt 并将 file2.txt 重命名为 file.txt
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
是一个流行的选择,有一些很好的教程和扎实的数学基础,这也是我的选择。
这里发生了几件事
您在 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
@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
我正在尝试读取文件的内容,将文本转为大写,然后写回。
这是我写的代码:
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)
。我怎样才能让它工作,为什么它在这两种情况下都不起作用?
我认为你的问题是 hGetContents 是懒惰的。当您使用 hGetContents 时,文件的内容不会立即读入。他们会在需要时读入。
在您的第一个示例中,您打开文件并说您需要其中的内容,但在对它们执行任何操作之前关闭了文件。然后你写入文件。当您写入现有文件时,内容会被清除,但由于您关闭了文件,您将无法再访问文件内容。
在第二个示例中,您打开了文件,然后尝试写入文件,但是因为内容直到需要时才真正被读取(当它们被转换和写回时),您最终尝试写入到并同时读取同一个文件。
您可以写入名为 file2.txt 的文件,完成后删除 file.txt 并将 file2.txt 重命名为 file.txt
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
是一个流行的选择,有一些很好的教程和扎实的数学基础,这也是我的选择。
这里发生了几件事
您在 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
@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