whichコマンドを実装してみました。
ファイルパスの扱いが面倒なので、動作するのはWindows環境のみです。簡単そうなコマンドなのですが、あんまりシンプルになりませんでした。こういうとりあえず全部展開してから後でフィルタかけるようなプログラムを書いていると、遅延評価は素晴しいと実感しますね。効率とか難しいこと考えなくてもいいので。
module Main (main) where import Control.Monad (filterM) import System.Directory (doesFileExist) import System.Environment (getArgs, getEnv, getProgName) import System.Exit (exitFailure) import System.IO (hPutStrLn, stderr) splitEnvPath :: String -> [FilePath] splitEnvPath [] = [] splitEnvPath s = let (s1, s2) = span (/= ';') $ dropWhile (== ';') s in s1 : splitEnvPath s2 addPath :: FilePath -> FilePath -> FilePath addPath p1 p2 = let sep = if last p1 == '\\' then "" else "\\" in p1 ++ sep ++ p2 extracttFilePaths :: [FilePath] -> [FilePath] -> [FilePath] extracttFilePaths cmds = concatMap (flip f cmds) where f path = concatMap (map (addPath path) . f') f' cmd | all (/= '.') cmd = map (cmd ++) ["", ".exe", ".bat", ".com"] | otherwise = [cmd] usage :: String -> IO a usage p = do hPutStrLn stderr $ "usage: " ++ p ++ " [-a] command..." exitFailure main :: IO () main = do path <- getEnv "PATH" prog <- getProgName args <- getArgs (allf, cmds) <- case args of [] -> usage prog ("-a" : []) -> usage prog ("-a" : cmds) -> return (id, cmds) cmds -> return (take 1, cmds) founds <- filterM doesFileExist $ extracttFilePaths cmds $ splitEnvPath path mapM_ putStrLn $ allf founds
実行例:
$ ./which.exe tree c:\usr\home\s-tanaka\bin\tree.exe $ ./which.exe -a tree c:\usr\home\s-tanaka\bin\tree.exe c:\WINDOWS\system32\tree.com