4

Haskell 实战:惰性地读取子进程输出

 3 years ago
source link: https://blog.lilydjwg.me/2012/4/18/haskell-in-action-read-subprocess-output-lazily.33103.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Haskell 实战:惰性地读取子进程输出

本文来自依云's Blog,转载请注明。

突然想给 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

改进版的完整程序在此

发送到 Kindle

Category: Haskell | Tags: Haskell linux | Read Count: 8732

评论 (5)
MaskRay 说:
9 年前

期待你再改一下 indet/haskell.vim ,
单行 where 缩进两格,下一行缩进两格
do 也是缩进两格

两格 = shiftwidth / 2

像 emacs 的 haskell-mode 那样

f = f
where
f = f

g = do
return f

依云 说:
9 年前

我这里的 Emacs haskell-mode 里 where 缩进了四个空格呀。我的 shiftwidth 为 2,刚好和你想要的一样。再除以 2 的话就只剩下一个空格了。而 sw=3 的情况下怎么办呢?

MaskRay 说:
9 年前

催你更新……
{ xx
, yy
} 这个括号会被左移 shiftwidth 格

依云 说:
9 年前

真麻烦啊……你能给我个完整的 Haskell 缩进规则之类的材料吗?
另外,你介意我使用 Python 脚本吗?

MaskRay 说:
9 年前

你有意向改真的太好了……规则看这个就好 http://en.wikibooks.org/wiki/Haskell/Indentation

你的 haskell.vim 已经挺不错了, record 类型很多人喜欢一行一个行首的逗号,都和 { 对齐,而结尾的 } 在你的 haskell.vim 不会和 { 对齐,而是减少了缩进

全世界我就找到你用 haskell 用 vim 有折腾能力愿意分享,python ruby 啥随意,依赖随意,毕竟是靠你提供插件……

[取消回复评论]

昵称 登录 E-mail: *
Web:
Twitter:
当有新评论通过 E-mail 通知我

loading captcha image...
(输入验证码)

or Ctrl+Enter


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK