if have list of filepaths, how can filter them return ones regular files (namely, not symlinks or directories)?
for example, using getdirectorycontents
main = contents <- getdirectorycontents "/foo/bar" let onlyfiles = filterfunction contents in print onlyfiles where "filterfunction" function returns filepaths represent files.
the answer may work on linux, cross platform support preferred.
[edit] using doesdirectoryexist doesn't work expected. script prints list of in directory, not files:
module main import system.directory import control.monad (filterm, liftm) getfiles :: filepath -> io [filepath] getfiles root = contents <- getdirectorycontents root fileshere <- filterm (liftm not . doesdirectoryexist) contents subdirs <- filterm doesdirectoryexist contents return fileshere main = files <- getfiles "/" print $ files additionally, variable subdirs contain "." , "..".
to find standard library functions, hoogle great resource; it's haskell search engine lets search by type. using requires figuring out how think types haskell way™, though, proposed type signatures doesn't quite work with. so:
you're looking
[filepath] -> [filepath]. remember, haskell spellingfilepath. so…you're looking
[filepath] -> [filepath]. unnecessary; if want filter things, should usefilter. so…you're looking function of type
filepath -> boolcan passfilter. can't quite right: function needs query file system, effect, , haskell tracks effects in type system usingio. so…you're looking function of type
filepath -> io bool.
and if search on hoogle, first result doesfileexist :: filepath -> io bool system.directory. docs:
the operation
doesfileexistreturnstrueif argument file exists , not directory, ,falseotherwise.
so system.directory.doesfileexist want. (well… little work! see below.)
now, how use it? can't use filter here, because have effectful function. use hoogle again – if filter has type (a -> bool) -> [a] -> [a], annotating results of functions monad m gives new type monad m => (a -> m bool) -> [a] -> m [bool] – there's easier "cheap trick". in general, if func function effectful/monadic version, effectful/monadic version called funcm, , lives in control.monad.¹ , indeed, there function control.monad.filterm :: monad m => (a -> m bool) -> [a] -> m [a].
however! hate admit it, in haskell, types don't provide information need. importantly, we're going have problem here:
- file paths given arguments functions interpreted relative current directory, but…
getdirectorycontentsreturns paths relative argument.
thus, there 2 approaches can take fix things. first adjust results of getdirectorycontents can interpreted correctly. (we discarding . , .. results, although if you're looking regular files won't hurt anything.) return file names include directory contents being examined. adjust getdirectorycontents function looks this:
getqualifieddirectorycontents :: filepath -> io [filepath] getqualifieddirectorycontents fp = map (fp </>) . filter (`notelem` [".",".."]) <$> getdirectorycontents fp the filter gets rid of special directories, , map prepends argument directory results. makes returned files acceptable arguments doesfileexist. (if haven't seen them before, (system.filepath.</>) appends 2 file paths; , (control.applicative.<$>), available (data.functor.<$>), infix synonym fmap, liftm more broadly applicable.)
putting together, final code becomes:
import control.applicative import control.monad import system.filepath import system.directory getqualifieddirectorycontents :: filepath -> io [filepath] getqualifieddirectorycontents fp = map (fp </>) . filter (`notelem` [".",".."]) <$> getdirectorycontents fp main :: io () main = contents <- getqualifieddirectorycontents "/foo/bar" onlyfiles <- filterm doesfileexist contents print onlyfiles or, if feel being fancy/point-free:
import control.applicative import control.monad import system.filepath import system.directory getqualifieddirectorycontents :: filepath -> io [filepath] getqualifieddirectorycontents fp = map (fp </>) . filter (`notelem` [".",".."]) <$> getdirectorycontents fp main :: io () main = print =<< filterm doesfileexist =<< getqualifieddirectorycontents "/foo/bar" the second approach adjust things doesfileexist runs appropriate current directory. return file name relative directory contents being examined. this, want use withcurrentdirectory :: filepath -> io -> io a function (but see below), , pass getdirectorycontents current directory "." argument. documentation withcurrentdirectory says (in part):
run
ioaction given working directory , restore original working directory afterwards, if given action fails due exception.
putting gives following code
import control.monad import system.directory main :: io () main = withcurrentdirectory "/foo/bar" $ print =<< filterm doesfileexist =<< getdirectorycontents "." this want, unfortunately, it's available in version 1.3.2.0 of directory package – of writing, recent one, , not 1 have. luckily, it's easy function implement; such set-a-value-locally functions implemented in terms of control.exception.bracket :: io -> (a -> io b) -> (a -> io c) -> io c. bracket function run bracket before after action, , correctly handles exceptions. can define withcurrentdirectory ourselves:
withcurrentdirectory :: filepath -> io -> io withcurrentdirectory fp m = bracket getcurrentdirectory setcurrentdirectory $ \_ -> setcurrentdirectory fp m and use final code:
import control.exception import control.monad import system.directory withcurrentdirectory :: filepath -> io -> io withcurrentdirectory fp m = bracket getcurrentdirectory setcurrentdirectory $ \_ -> setcurrentdirectory fp m main :: io () main = withcurrentdirectory "/foo/bar" $ print =<< filterm doesfileexist =<< getdirectorycontents "." also, 1 quick note lets in dos: in do block,
do ...foo... let x = ...bar... ...baz... is equivalent to
do ...foo... let x = ...bar... in ...baz... so example code doesn't need in in let , can outdent print call.
¹ not always: want different classes of effects! use applicative control.applicative when possible; more things applicatives monads (although means can less them). in case, effectful functions may live there, or in data.foldable or data.traversable.
Comments
Post a Comment