【问题标题】:How to retrieve output of external program executed from Haskell?如何检索从 Haskell 执行的外部程序的输出?
【发布时间】:2014-01-30 18:27:44
【问题描述】:

我想从 Haskell 运行一个外部程序并检索其输出和错误流的内容。在其中一个库中,我找到了以下代码:

runProcess :: FilePath -> [String] -> IO (ExitCode, String, String)
runProcess prog args = do
  (_,o,e,p) <- runInteractiveProcess prog args Nothing Nothing
  hSetBuffering o NoBuffering
  hSetBuffering e NoBuffering
  sout  <- hGetContents o
  serr  <- hGetContents e
  ecode <- length sout `seq` waitForProcess p
  return (ecode, sout, serr)

这是正确的做法吗?

这里有一些我不明白的地方:为什么流设置为NoBuffering?为什么length sout `seq`?这感觉像是某种黑客行为。

另外,我想将输出和错误流合并为一个,以获得与在命令行上执行2&gt;&amp;1 相同的效果。如果可能,我想避免使用专用的 I/O 库并依赖 GHC 提供的标准包。

【问题讨论】:

  • 我不确定是否可以获得与在 shell 中执行 2&gt;&amp;1 相同的效果。你有什么特别的理由想要这样吗?
  • 我想说seq 的原因是强制消耗程序的完整输出。否则(因为字符串是惰性的)可能会发生死锁:子进程将等待写入其输出,而主进程将等待进程完成。但似乎serr 可能存在同样的问题,我担心如果程序将大量输出写入其标准错误,上述代码也会挂起。做到这一点可能不会很简单,所以我可能宁愿寻找一些已经解决问题的稳定 IO 库。
  • 您还需要程序是多平台的,还是针对特定平台?
  • process 库有函数 readProcessWithExitCode 以字符串形式返回 stdoutstderr。如果进程写入大量输出,如果您想在程序运行时访问输出,或者如果您希望 stdoutstderr 交错,那么这还不够。
  • 如果您使用runProcess 并为stdout 和stderr 传递相同的句柄(而不是Nothing),它应该像2&gt;&amp;1 一样工作。

标签: haskell io


【解决方案1】:

我觉得readProcessWithExitCode 非常简洁。

这是一个仅使用 GHC 标准库中的函数的示例。该程序列出了按大小排序的主目录的文件,打印进程的退出代码以及标准输出和标准错误流的内容:

import           System.Directory               ( getHomeDirectory )
import           System.Process                 ( readProcessWithExitCode )
import           System.Exit                    ( ExitCode )
import           Data.List.NonEmpty

callCmd :: NonEmpty String -> IO (ExitCode, String, String)
callCmd (cmd :| args) = readProcessWithExitCode cmd args stdIn
  where stdIn = ""

main = do
  home                       <- getHomeDirectory
  (exitCode, stdOut, stdErr) <-
    callCmd $ "ls" :| [home, "--almost-all", "-l", "-S"]
  putStrLn
    $  "Exit code: "
    ++ show exitCode
    ++ "\nOut: "
    ++ stdOut
    ++ "\nErr: "
    ++ stdErr

【讨论】:

    【解决方案2】:

    此示例程序使用processasyncpipespipes-bytestring 包来执行外部命令,并在命令运行时写入stdoutstderr 来分隔文件:

    import Control.Applicative
    import Control.Monad
    import Control.Concurrent
    import Control.Concurrent.Async
    import Control.Exception
    import Pipes
    import qualified Pipes.ByteString as P
    import Pipes.Concurrent
    import System.Process
    import System.IO
    
    writeToFile :: Handle -> FilePath -> IO ()
    writeToFile handle path = 
        finally (withFile path WriteMode $ \hOut ->
                    runEffect $ P.fromHandle handle >-> P.toHandle hOut)
                (hClose handle) 
    
    main :: IO ()
    main = do
       (_,mOut,mErr,procHandle) <- createProcess $ 
            (proc "foo" ["--help"]) { std_out = CreatePipe
                                    , std_err = CreatePipe 
                                    }
       let (hOut,hErr) = maybe (error "bogus handles") 
                               id
                               ((,) <$> mOut <*> mErr)
       a1 <- async $ writeToFile hOut "stdout.txt" 
       a2 <- async $ writeToFile hErr "stderr.txt" 
       waitBoth a1 a2
       return ()
    

    这是一种将stdoutstderr 交错写入同一文件的变体:

    writeToMailbox :: Handle -> Output ByteString -> IO ()
    writeToMailbox handle oMailbox = 
         finally (runEffect $ P.fromHandle handle >-> toOutput oMailbox)
                 (hClose handle) 
    
    writeToFile :: Input ByteString -> FilePath -> IO ()
    writeToFile iMailbox path = 
        withFile path WriteMode $ \hOut ->
             runEffect $ fromInput iMailbox >-> P.toHandle hOut
    
    main :: IO ()
    main = do
       (_,mOut,mErr,procHandle) <- createProcess $ 
            (proc "foo" ["--help"]) { std_out = CreatePipe
                                    , std_err = CreatePipe 
                                    }
       let (hOut,hErr) = maybe (error "bogus handles") 
                               id
                               ((,) <$> mOut <*> mErr)
       (mailBoxOut,mailBoxIn,seal) <- spawn' Unbounded
       a1 <- async $ writeToMailbox hOut mailBoxOut 
       a2 <- async $ writeToMailbox hErr mailBoxOut 
       a3 <- async $ waitBoth a1 a2 >> atomically seal 
       writeToFile mailBoxIn "combined.txt" 
       wait a3
       return ()
    

    它使用pipes-concurrent。耗尽每个句柄的线程写入同一个邮箱。邮箱由主线程读取,主线程在命令运行时写入输出文件。

    【讨论】:

      【解决方案3】:

      使用 Shelly,一个用于在 Haskell 中进行类 shell 编程的模块:

      http://hackage.haskell.org/package/shelly-1.4.1/docs/Shelly.html

      【讨论】:

        猜你喜欢
        • 2014-11-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-08-12
        • 1970-01-01
        • 1970-01-01
        • 2021-09-12
        • 2017-08-15
        相关资源
        最近更新 更多