英文:
Deleting the last line of a file in Haskell
问题
这是翻译好的部分:
"我是Haskell的初学者,正在尝试将我的一个shell脚本重写为Haskell作为测试之一。我尝试实现的一个目标是删除文件的最后一行并读取其值。
我尝试使用readFile
然后writeFile
,但无法成功,因为它是惰性的。
我能够通过以下代码使其工作:
import Data.List
import qualified Data.ByteString.Char8 as B
import qualified Data.Text as T
import qualified Data.Text.Encoding as TL
erase filename = do
contents <- B.readFile filename
let array = map (T.unpack . TL.decodeUtf8) (B.lines contents)
B.writeFile filename $ B.pack (intercalate "\n" (init array))
print $ last array
这将文件的行转换为列表,然后在不包括最后一行的情况下写入文件。它使用了ByteString,所以不会出现惰性问题。然而,我认为这种方法非常冗长,写法奇怪。是否有一些从文件中删除一行的函数,或者如何改进这段代码呢?"
英文:
I am a beginner to Haskell and am trying to rewrite one of my shell scripts in Haskell as a test. One of the things I am trying to accomplish is removing the last line of a file and reading its value.
I tried to use readFile
and then writeFile
, but I can't because it is lazy.
I was able to get it to work using this code:
import Data.List
import qualified Data.ByteString.Char8 as B
import qualified Data.Text as T
import qualified Data.Text.Encoding as TL
erase filename = do
contents <- B.readFile filename
let array = map (T.unpack . TL.decodeUtf8) (B.lines contents)
B.writeFile filename $ B.pack (intercalate "\n" (init array))
print $ last array
This turns the lines of the file into a list and then writes to the file without the last line. It is using the ByteString so it won't be lazy. However, I think that this way is very verbose and does it weirdly. Is there some function that deletes a line from a file or how can this code be improved?
答案1
得分: 3
我建议不将内容写入同一个文件:大多数Linux命令通常都会写入不同的文件,然后可选择将目标文件移动到原文件上。例如,如果你排序,你始终会有原文件,或者结果文件之一。
我们可以实现一个辅助函数来获取第一行和最后一行的内容:
safeUnsnoc :: a -> [a] -> ([a], a)
safeUnsnoc d = go
where go [] = ([], d)
go [x] = ([], x)
go (x:xs) = (x:a, b) where ~(a, b) = go xs
这通常更好:它将避免在列表上两次枚举,因此可以更节省内存。
然后,我们可以从文件中读取内容,将文件存储在临时文件中,然后进行文件移动:
import System.Directory (renameFile)
import System.IO.Temp (writeSystemTempFile)
erase filename = do
(content, lastLine) <- safeUnsnoc "" . lines <$> readFile filename
tmp <- writeSystemTempFile "temp-init" (unlines content)
renameFile tmp filename
putStrLn lastLine
英文:
I would advise not to write to the same file: most linux commands will normally always write to a different file, and optionally then move the target file over the original file. For example if you sort, you always have at least or the original file, or the result file.
We can implement a helper function to obtain the content of the first lines and the last line:
safeUnsnoc :: a -> [a] -> ([a], a)
safeUnsnoc d = go
where go [] = ([], d)
go [x] = ([], x)
go (x:xs) = (x:a, b) where ~(a, b) = go xs
This is often better: it will avoid enumerating two times over the list, so this can be more memory conservative.
Then we thus can read from the file, store the file in a temporary file, and do the file move:
import System.Directory(renameFile)
import System.IO.Temp(writeSystemTempFile)
erase filename = do
(content, lastLine) <- safeUnsnoc "" . lines <$> readFile filename
tmp <- writeSystemTempFile "temp-init" (unlines content)
renameFile tmp filename
putStrLn lastLine
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论