在Haskell中如何在文件开头写入内容

9

我正在尝试在文件开头添加一个数字,但是"appendFile"函数却将内容添加到文件末尾。

我写了以下代码但没有成功。

myAppendFile file = do x <- readFile file
                   writeFile file "1"
                   appendFile file x
                   return x

当我执行以下操作时:
*main> myAppendFile "File.txt"

错误提示为:
the ressource is busy (file is locked)

所以,我该如何在文件开头写入内容?

1
这个有用吗?https://mail.haskell.org/pipermail/haskell-cafe/2010-June/078666.html - Daenyth
4
问题在于readFile是按需读取文件内容的,而底层文件处理程序仍然保持在执行writeFile时。解决方法是要求完整的文件内容,以便在执行writeFile之前关闭处理程序。在我看来,更简单的方法是使用严格IO,在这种情况下你可能会发现strict很有用(代价是将所有文件内容作为字符列表保存在内存中)。 - Javran
3
@Javran:你能把那个写成答案吗?它很好地解决了潜在问题。谢谢! - Tikhon Jelvis
https://dev59.com/SnE85IYBdhLWcg3w9ohJ - user2388535
@TikhonJelvis 刚刚添加了,希望你喜欢 :) - Javran
@Javran:没错,你很好地解释了实际发生的事情。 - Tikhon Jelvis
3个回答

8
正如Gabriel所指出的,问题在于readFile按需读取文件内容,并且只有在文件内容完全消耗时才关闭底层文件句柄。
解决方案是要求完整的文件内容,以便在执行writeFile之前关闭处理程序。
简短回答:使用来自严格包的readFile
import qualified System.IO.Strict as SIO
import Data.Functor

myAppendFile :: FilePath -> IO String
myAppendFile file = do
    x <- SIO.readFile file
    writeFile file "1"
    appendFile file x
    return x

main :: IO ()
main = void $ myAppendFile "data"

另一种简单的“技巧”是使用seq来实现:
myAppendFile :: FilePath -> IO String
myAppendFile file = do
    x <- readFile file
    length x `seq` writeFile file "1"
    appendFile file x
    return x
seq a b基本上就是将b加上一个要求b时,先计算a(在这种情况下是length x)的WHNF,然后再检查b。这是因为length需要其参数(x在这种情况下)被遍历,直到可以看到空列表[],所以需要获取完整的文件内容。
请注意,使用严格IO,您的程序将在内存中保存所有文件内容(作为Char列表)。对于玩具程序来说,这很好,但如果您关心性能,可以查看bytestringtext,取决于您的程序是否处理字节或文本(如果要处理Unicode文本)。它们都比String更有效率。

谢谢您的回答,但我该如何使用System.IO.Strict?我需要安装什么吗? - Jacko
1
@Raphael 是的,你需要安装 strict。只需输入 cabal install strict(将安装在您的主目录下某个位置,无需 root 权限)。 - Javran

5
我成功让它工作了,但是我使用了严格数据类型ByteString:
import Data.ByteString.Char8 as B

myAppendFile file = do
    x <- B.readFile file
    B.writeFile file $ B.pack "1"
    B.appendFile file x
    return $ B.unpack x

main = myAppendFile "c:\\test.txt"

您的代码出现了错误,这是因为Haskell是惰性求值的。当您试图将数据写入文件时,由于Haskell不需要在代码的那个点之前使用变量x,所以数据没有完全读取完,导致Haskell仍然占用文件。
另外还有一个观察结果,如果在此函数之前有另一个惰性读取,请将其替换为严格版本,否则您将再次遇到麻烦。

在这里使用 Char8 真的合适吗?我认为它会在 Unicode 文件中失败(Char8 截断)。 - bennofs
1
@bennofs它不会失败,因为Unicode字符只是一个或多个Char8粘在一起。 - Gabriel Ciubotaru
哦,你是对的。看着这段代码,实际上它应该可以工作。我被那个模块上的“所有字符将被截断为8位”的注释吓到了。 - bennofs
谢谢您的回答。您能解释一下"$ B.pack"的意思吗? - Jacko
1
@Raphael B.pack将字符串转换为Byte8。$ B.pack "1"与(B.pack "1")相同。简单来说,$在行末打开(并在行末关闭)。 - Gabriel Ciubotaru

1
传统的方法在Haskell中同样适用。创建一个新的临时文件,将字节从旧文件流式传输到临时文件,然后将临时文件移动到旧文件上。您可以/应该使用ByteString或类似的东西来提高效率,但您可以分块复制,而不需要将所有内容读入内存,然后再输出。我认为pipesconduit都提供了界面,使这更加愉快。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接