哈斯克尔诺布在这里。我有一个特别关于如何使用现有库的问题,这可能会导致正确使用Haskell的一些更基本的方面。
我正在学习Haskell并在学习的过程中考虑一个小项目。该脚本需要查找给定目录中的所有tarball并将其并行解压缩。在这一点上,我正在研究解包的基本功能。那么,使用Codec.Archive.Tar包,如何使用完全限定的路径覆盖有关tarball的行为?
以下是一些示例代码:
module Main where
import qualified Codec.Archive.Tar as Tar
import qualified Codec.Compression.GZip as GZip
import Control.Monad (liftM, unless)
import qualified Data.ByteString.Lazy as BS
import System.Directory (doesDirectoryExist, getDirectoryContents)
import System.Exit (exitWith, ExitCode(..))
import System.FilePath.Posix (takeExtension)
searchPath = "/home/someuser/tarball/dir"
exit = exitWith ExitSuccess
die = exitWith (ExitFailure 1)
processFile :: String -> IO ()
processFile file = do
putStrLn $ "Unpacking " ++ file ++ " to " ++ searchPath
Tar.unpack searchPath . Tar.read . GZip.decompress =<< BS.readFile filePath
where filePath = searchPath ++ "/" ++ file
main = do
dirExists <- doesDirectoryExist searchPath
unless dirExists $ (putStrLn $ "Error: Search path not found: " ++ searchPath) >> die
files <- targetFiles `liftM` getDirectoryContents searchPath
mapM_ processFile files
exit
where targetFiles = filter (\f -> f /= "." && f /= ".." && takeExtension f == ".tgz")
当我在包含以下内容的tarball的目录中运行它时:
tar czvPf myfile.tgz /tarball_testing/myfile
我得到以下输出:
Unpacking myfile.tgz to /tarball_testing
unpacker.hs: Absolute file name in tar archive: "/tarball_testing/myfile"
第二行是问题。阅读Codec.Archive.Tar的文档我没有看到禁用此功能的方法(对于我为什么要在tarball中使用完整路径的讨论,或者这样做的相对安全含义不感兴趣)。
我想到的第一件事就是我不知何故需要覆盖这个功能,但这并没有感觉到#34;就像职业Haskeller那样做的方式。我能指向正确的方向吗?
答案 0 :(得分:3)
你不能monkey patch或以其他方式覆盖Haskell模块中的函数,因此没有解决方法可以让你避免库的安全措施。但是,您可以使用Codec.Archive.Tar
中的功能在解压缩之前修改tar条目路径,以便它们不再是绝对路径。具体来说,有mapEntriesNoFail函数,类型为
mapEntriesNoFail :: (Entry -> Entry) -> Entries e -> Entries e
Entries
是Tar.unpack
的参数类型,而Entry
是单个条目的类型。感谢mapEntriesNoFail
,我们的问题就是编写Entry -> Entry
函数来调整路径。为此,首先我们需要一些额外的进口:
import qualified Codec.Archive.Tar.Entry as Tar
import System.FilePath.Posix (takeExtension, dropDrive, hasTrailingPathSeparator)
import Data.Either (either)
该功能可以如下所示:
dropDriveFromEntry :: Tar.Entry -> Tar.Entry
dropDriveFromEntry entry =
either (error "Resulting tar path is somehow too long")
(\tp -> entry { Tar.entryTarPath = tp })
drivelessTarPath
where
tarPath = Tar.entryTarPath entry
path = Tar.fromTarPath tarPath
toTarPath' p = Tar.toTarPath (hasTrailingPathSeparator p) p
drivelessTarPath = toTarPath' $ dropDrive path
这似乎有点啰嗦;然而,我们跳过的箍是为了确保最终的沥青路径是合理的。您可以在Codec.Archive.Tar.Entry
文档中阅读有关tar处理的血腥细节。此定义中的关键函数是dropDrive
,它使绝对路径相对(在Linux中,它删除绝对路径的前导斜杠)。
值得在either
的使用上花几句话。 toTarPath
生成类型为Either String TarPath
的值,以说明失败的可能性。具体而言,如果提供的路径太长,则转换为tar路径会失败。但是,在我们的例子中,路径不能太长,因为它是一个已经存在于tar文件中的路径,可能带有删除的前导斜杠。既然如此,使用Either
消除either
包装就足够了,传递错误而不是函数来处理(不可能的)Left
情况。
手持dropDriveFromEntry
时,我们只需在解压前将其映射到条目上。您的计划的相关部分将成为:
Tar.unpack searchPath . Tar.mapEntriesNoFail dropDriveFromEntry
. Tar.read . GZip.decompress =<< BS.readFile filePath
请注意,如果dropDriveFromEntry
中存在相关错误,我们会将其返回Either String TarPath
,然后使用mapEntries
代替mapEntriesNoFail
。
通过这些更改,tar文件中的条目将被提取到/home/someuser/tarball/dir/tarball_testing/myfile
。如果这不是您的意图,您可以修改dropDriveFromEntry
,以便它执行您需要的任何额外路径处理。
P.S。:关于你问题的替代标题,并考虑到你向我们展示的合理的小程序,我认为你不应该担心:)