突然想给 locate 命令写个 wrapper,把输出中的家目录和一些因加密而引入的软链接显示为~
。自然,这需要读取 locate 命令的输出。在 process 这个库中看到了readProcess
函数,似乎是自己想要的(完整代码):
1 2 3 4 | 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
为什么不是惰性的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | 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
好了:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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 |
改进后的程序,不会等待进程结束,而是返回输出和进程句柄。进程句柄用来等待子进程结束,同时获取退出状态。至于那个管道就不关闭了,留给操作系统解决好了。
1 2 3 4 | main = do (out, p) <- doLocate putStr $ transform out waitForProcess p >>= exitWith |
改进版的完整程序在此。