突然想给 locate 命令写个 wrapper,把输出中的家目录和一些因加密而引入的软链接显示为~
。自然,这需要读取 locate 命令的输出。在 process 这个库中看到了readProcess
函数,似乎是自己想要的(完整代码):
readLocate :: [String] -> IO String readLocate args = getArgs >>= \cmd -> let args' = args ++ cmd in readProcess "locate" args' ""
结果却发现,原本 locate 命令是边查找边输出的,现在变成了先静默,然后一下子全部吐出来。没有按 Haskell 惯常的「懒惰」脾气来。这样一来,当我发现输出项目太多想按Ctrl-C中断时已经晚了。
Google 了一下,找到这个:
I guess people who want laziness can implement it themselves directly, taking care to get whatever laziness it is that they want.
好吧。我先下回 process 库的源码看看readProcess
为什么不是惰性的:
readProcess :: FilePath -- ^ command to run -> [String] -- ^ any arguments -> String -- ^ standard input -> IO String -- ^ stdout readProcess cmd args input = do (Just inh, Just outh, _, pid) <- createProcess (proc cmd args){ std_in = CreatePipe, std_out = CreatePipe, std_err = Inherit } -- fork off a thread to start consuming the output output <- hGetContents outh outMVar <- newEmptyMVar _ <- forkIO $ C.evaluate (length output) >> putMVar outMVar () -- now write and flush any input when (not (null input)) $ do hPutStr inh input; hFlush inh hClose inh -- done with stdin -- wait on the output takeMVar outMVar hClose outh -- wait on the process ex <- waitForProcess pid case ex of ExitSuccess -> return output ExitFailure r -> ioError (mkIOError OtherError ("readProcess: " ++ cmd ++ ' ':unwords (map show args) ++ " (exit " ++ show r ++ ")") Nothing Nothing)
原来是另开了一 IO 线程读输出,然后等待进程结束后关闭管道。这解释为什么它不是惰性的——它得进程善后处理。
那好吧,改用createProcess
好了:
doLocate :: IO (String, ProcessHandle) doLocate = do argv0 <- getProgName let args = case argv0 of "lre" -> ["-b", "--regex"] _ -> [] args' <- getArgs let args'' = args ++ args' (_, Just out, _, p) <- createProcess (proc "locate" args''){ std_in = Inherit, std_out = CreatePipe, std_err = Inherit } hSetBuffering out LineBuffering (,) <$> hGetContents out <*> return p
改进后的程序,不会等待进程结束,而是返回输出和进程句柄。进程句柄用来等待子进程结束,同时获取退出状态。至于那个管道就不关闭了,留给操作系统解决好了。
main = do (out, p) <- doLocate putStr $ transform out waitForProcess p >>= exitWith
改进版的完整程序在此。