使用管道递归列出目录下的所有文件
Listing all the files under a directory recursively, using Pipes
我读完了Pipes tutorial,我想写一个函数来递归地列出目录中的所有文件。我尝试使用以下代码:
enumFiles :: FilePath -> Producer' FilePath (PS.SafeT IO) ()
enumFiles path =
PS.bracket (openDirStream path) (closeDirStream) loop
where
loop :: DirStream -> Producer' FilePath (PS.SafeT IO) ()
loop ds = PS.liftBase (readDirStream ds) >>= checkName
where
checkName :: FilePath -> Producer' FilePath (PS.SafeT IO) ()
checkName "" = return ()
checkName "." = loop ds
checkName ".." = loop ds
checkName name = PS.liftBase (getSymbolicLinkStatus newPath)
>>= checkStat newPath
where newPath = path </> name
checkStat path stat
| isRegularFile stat = yield path >> loop ds
| isDirectory stat = enumFiles path
| otherwise = loop ds
然而,一旦达到 return ()
,此生产者将终止。我想我没有以正确的方式编写它,但我没有看到这样做的正确方法是什么。
只需更改此行:
| isDirectory stat = enumFiles path
到
| isDirectory stat = enumFiles path >> loop ds
代码在这种递归情况下缺少递归。
您也可以将这个生产者分解成更小的生产者和管道的组合:
{-# LANGUAGE RankNTypes #-}
module Main where
import qualified Pipes.Prelude as P
import qualified Pipes.Safe as PS
import Control.Monad
import Pipes
import System.FilePath.Posix
import System.Posix.Directory
import System.Posix.Files
readDirStream' :: FilePath -> Producer' FilePath (PS.SafeT IO) ()
readDirStream' dirpath =
PS.bracket (openDirStream dirpath) closeDirStream (forever . loop)
where
loop stream =
liftIO (readDirStream stream) >>= yield
enumFiles :: FilePath -> Producer' FilePath (PS.SafeT IO) ()
enumFiles path =
readDirStream' path
>-> P.takeWhile (/= "")
>-> P.filter (not . flip elem [".", ".."])
>-> P.map (path </>)
>-> forever (do
entry <- await
status <- liftIO $ getSymbolicLinkStatus entry
when (isDirectory status) (enumFiles entry)
when (isRegularFile status) (yield entry))
main :: IO ()
main =
PS.runSafeT $ runEffect (enumFiles "/tmp" >-> P.stdoutLn)
我发现使用 Control.Monad
中的 forever
或 Pipe.Prelude
中的组合器之一代替手动递归通常很有帮助;它有助于减少像这样的小错别字。然而,正如孩子们所说,你的里程可能会相差很大。
我读完了Pipes tutorial,我想写一个函数来递归地列出目录中的所有文件。我尝试使用以下代码:
enumFiles :: FilePath -> Producer' FilePath (PS.SafeT IO) ()
enumFiles path =
PS.bracket (openDirStream path) (closeDirStream) loop
where
loop :: DirStream -> Producer' FilePath (PS.SafeT IO) ()
loop ds = PS.liftBase (readDirStream ds) >>= checkName
where
checkName :: FilePath -> Producer' FilePath (PS.SafeT IO) ()
checkName "" = return ()
checkName "." = loop ds
checkName ".." = loop ds
checkName name = PS.liftBase (getSymbolicLinkStatus newPath)
>>= checkStat newPath
where newPath = path </> name
checkStat path stat
| isRegularFile stat = yield path >> loop ds
| isDirectory stat = enumFiles path
| otherwise = loop ds
然而,一旦达到 return ()
,此生产者将终止。我想我没有以正确的方式编写它,但我没有看到这样做的正确方法是什么。
只需更改此行:
| isDirectory stat = enumFiles path
到
| isDirectory stat = enumFiles path >> loop ds
代码在这种递归情况下缺少递归。
您也可以将这个生产者分解成更小的生产者和管道的组合:
{-# LANGUAGE RankNTypes #-}
module Main where
import qualified Pipes.Prelude as P
import qualified Pipes.Safe as PS
import Control.Monad
import Pipes
import System.FilePath.Posix
import System.Posix.Directory
import System.Posix.Files
readDirStream' :: FilePath -> Producer' FilePath (PS.SafeT IO) ()
readDirStream' dirpath =
PS.bracket (openDirStream dirpath) closeDirStream (forever . loop)
where
loop stream =
liftIO (readDirStream stream) >>= yield
enumFiles :: FilePath -> Producer' FilePath (PS.SafeT IO) ()
enumFiles path =
readDirStream' path
>-> P.takeWhile (/= "")
>-> P.filter (not . flip elem [".", ".."])
>-> P.map (path </>)
>-> forever (do
entry <- await
status <- liftIO $ getSymbolicLinkStatus entry
when (isDirectory status) (enumFiles entry)
when (isRegularFile status) (yield entry))
main :: IO ()
main =
PS.runSafeT $ runEffect (enumFiles "/tmp" >-> P.stdoutLn)
我发现使用 Control.Monad
中的 forever
或 Pipe.Prelude
中的组合器之一代替手动递归通常很有帮助;它有助于减少像这样的小错别字。然而,正如孩子们所说,你的里程可能会相差很大。