【问题标题】:Scala Process - Capture Standard Out and Exit CodeScala Process - 捕获标准输出和退出代码
【发布时间】:2021-09-28 02:34:14
【问题描述】:

我正在使用 Scala scala.sys.process 库。

我知道我可以使用! 捕获退出代码并使用!! 捕获输出,但是如果我想同时捕获两者怎么办?

我看到这个答案https://stackoverflow.com/a/6013932/416338 看起来很有希望,但我想知道是否有一个班轮,我错过了什么。

【问题讨论】:

  • RE:“也在寻找一种简单的方法来做到这一点......”。发布赏金与挥动魔杖不同;-)。我们面临的挑战并不总是简单的答案。
  • 实际上发布赏金挥舞着魔杖。你会得到对这个问题的极大关注。

标签: scala


【解决方案1】:

我有以下实用方法来运行命令:

import sys.process._
def runCommand(cmd: Seq[String]): (Int, String, String) = {
  val stdoutStream = new ByteArrayOutputStream
  val stderrStream = new ByteArrayOutputStream
  val stdoutWriter = new PrintWriter(stdoutStream)
  val stderrWriter = new PrintWriter(stderrStream)
  val exitValue = cmd.!(ProcessLogger(stdoutWriter.println, stderrWriter.println))
  stdoutWriter.close()
  stderrWriter.close()
  (exitValue, stdoutStream.toString, stderrStream.toString)
}

如您所见,它捕获标准输出、标准错误和结果代码。

【讨论】:

  • @cevaris - 使用以下脚本进行测试,两次都得到输出:pastebin.com/myvfwZWQ 你用什么来测试这个?
  • 以复杂的方式执行它,如果它对您有用,很可能是用户错误。现在要编辑我的评论。
【解决方案2】:

您可以使用ProcessIO。我在 Specs2 测试中需要类似的东西,我必须根据stdin 上的输入(inout 的类型为String)检查退出值以及进程的输出:

"the operation" should {
  f"return '$out' on input '$in'" in {
    var res = ""
    val io = new ProcessIO(
      stdin  => { stdin.write(in.getBytes)
                  stdin.close() }, 
      stdout => { res = convertStreamToString(stdout)
                  stdout.close() },
      stderr => { stderr.close() })
    val proc = f"$operation $file".run(io)
    proc.exitValue() must be_==(0)
    res must be_==(out)
  }
}

我想这可能会对你有所帮助。在示例中,我忽略了来自 stderr 的内容。

【讨论】:

  • ProcessIO 是内部类——它是不可访问的
  • 不这么认为。看看docs
【解决方案3】:

您可以指定捕获文本的输出流:

import sys.process._
val os   = new java.io.ByteArrayOutputStream
val code = ("volname" #> os).!
os.close()
val opt  = if (code == 0) Some(os.toString("UTF-8")) else None

【讨论】:

  • 这很好!不确定为什么不喜欢它。
  • 对我来说,如果命令失败,val code = ("volname" #> os).! 行就会挂起。
  • 但是,如果我将 ByteArrayOutputStream 替换为 File,它会起作用
  • @BenFradet - 我可以确认这挂在 Scala 2.12 REPL 中(我可能在编写时尝试使用 2.11)。对我来说,这看起来像是 Scala REPL 中的一个错误,例如,在 IntelliJ IDEA 中运行代码是可行的。
【解决方案4】:

BasicIO 或 ProcessLogger 的单行使用很有吸引力。

scala> val sb = new StringBuffer
sb: StringBuffer = 

scala> ("/bin/ls /tmp" run BasicIO(false, sb, None)).exitValue
res0: Int = 0

scala> sb
res1: StringBuffer = ...

scala> import collection.mutable.ListBuffer
import collection.mutable.ListBuffer

scala> val b = ListBuffer[String]()
b: scala.collection.mutable.ListBuffer[String] = ListBuffer()

scala> ("/bin/ls /tmp" run ProcessLogger(b append _)).exitValue
res4: Int = 0

scala> b mkString "\n"
res5: String = ...

根据捕获的含义,您可能对输出感兴趣,除非退出代码不为零。在这种情况下,处理异常。

scala> val re = "Nonzero exit value: (\\d+)".r.unanchored
re: scala.util.matching.UnanchoredRegex = Nonzero exit value: (\d+)

scala> Try ("./bomb.sh" !!) match {
     | case Failure(f) => f.getMessage match {
     |   case re(x) => println(s"Bad exit $x")
     | }
     | case Success(s) => println(s)
     | }
warning: there were 1 feature warning(s); re-run with -feature for details
Bad exit 3

【讨论】:

  • 我认为假设错误消息将始终完全是 Nonzero exit value: (...)...
  • 可以肯定的是,这是 API 中的一个缺陷,但幸运的是它是开源的,如果他们尝试更改它,您会注意到,您会说,嘿,人们依赖该消息!他们会恢复它。我试图记住我是否曾经依赖于进程的非零退出是 3 而不是 2 或 17。
  • “一点也不安全”可能是指“不完全安全”。当然,错误不必完全如此,而只包括模式。
  • @Alex import sys.process._
  • 这三个例子都有帮助。试图内化(“呆伯特:优化硬盘..现在..)
【解决方案5】:

“亚历克斯克鲁斯”在您的链接中提供的响应相当简洁,除非性能较差。

你可以扩展 sys.process.ProcessLogger 来管理

var out = List[String]()
var err = List[String]()

在内部,带有用于 out.reverse 和 err.reverse 结果的 getter。

【讨论】:

    【解决方案6】:

    这是一个非常简单的 Scala 包装器,它允许您检索标准输出、标准错误和退出代码。

    import scala.sys.process._
    
    case class ProcessInfo(stdout: String, stderr: String, exitCode: Int)
    
    object CommandRunner {
    
    def runCommandAndGetOutput(command: String): ProcessInfo = {
        val stdout = new StringBuilder
        val stderr = new StringBuilder
        val status = command ! ProcessLogger(stdout append _, stderr append _)
        ProcessInfo(stdout.toString(), stderr.toString(), status)
      }
    }
    

    【讨论】:

    • 当心:此解决方案不会捕获输出的格式(即删除换行符)
    【解决方案7】:

    我结合了这些并想出了这个。预期的 RC 在那里,因为我有一个程序需要在一个项目中运行,它在工作时返回 1。这确实取决于异常的文本,但它仍然会做一些不匹配的合理事情。

      private val ProcessErrorP: Regex = "(.*): error=(\\d+),(.*)".r.unanchored
    
      case class ProcessInfo(stdout: String, stderr: String, exitCode: Int, private val expectedRd: Int) {
        def succeeded: Boolean = exitCode == expectedRd
        def failed: Boolean = !succeeded
        def asOpt: Option[String] = if (succeeded) None else Some(stderr)
      }
    
      /**
       * Run a simple command
       * @param command -- what to run
       * @return -- what happened
       */
      def run(command: String, expectedRc: Int = 0): ProcessInfo = {
        try {
          val stdout = new StringBuilder
          val stderr = new StringBuilder
          val status = command ! ProcessLogger(stdout append _, stderr append _)
          ProcessInfo(stdout.toString(), stderr.toString(), status, expectedRc)
        } catch {
          case io: IOException =>
            val dm = io.getMessage
            dm match {
              case ProcessErrorP(message, code, reason) =>
                ProcessInfo("", s"$message, $reason", code.toInt, expectedRc)
              case m: String =>
                ProcessInfo("", m, 999, expectedRc)
            }
        }
      }
    

    【讨论】:

      猜你喜欢
      • 2010-09-11
      • 2012-02-04
      • 1970-01-01
      • 2012-07-16
      • 2015-05-08
      • 2015-11-21
      • 2013-08-07
      • 1970-01-01
      相关资源
      最近更新 更多