40

I'm working with the Scala scala.sys.process library.

I know that I can capture the exit code with ! and the output with !! but what if I want to capture both?

I've seen this answer https://stackoverflow.com/a/6013932/416338 which looks promising, but I'm wondering if there is a one liner and I'm missing something.

Community
  • 1
  • 1
Nick Long
  • 1,908
  • 3
  • 18
  • 28
  • 2
    RE: "also looking for a simple way to do this...". Posting a bounty isn't the same as waving a magic wand ;-). There are not always simple answers to the challenges we face. – Richard Sitze May 18 '13 at 18:24
  • 2
    Actually posting a bounty *is* waving a magic wand. You get a magical amount of attention to the question. – WestCoastProjects Jun 05 '15 at 03:58

7 Answers7

32

I have the following utility method for running commands:

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)
}

As you can see, it captures stdout, stderr and result code.

Jus12
  • 17,824
  • 28
  • 99
  • 157
Rogach
  • 26,050
  • 21
  • 93
  • 172
  • @cevaris - tested with the following script, got output both times: http://pastebin.com/myvfwZWQ What do you use for testing this? – Rogach May 10 '16 at 16:25
  • Was executing it in a complex way, most likely user error if it is working for you. Going to redact my comment for now. – cevaris May 11 '16 at 01:57
19

You can use ProcessIO. I needed something like that in a Specs2 Test, where I had to check the exit value as well as the output of a process depending on the input on stdin (in and out are of type 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)
  }
}

I figured that might help you. In the example I am ignoring what ever comes from stderr.

Martin Ring
  • 5,404
  • 24
  • 47
13

You can specify an output stream that catches the text:

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
Seth Tisue
  • 29,985
  • 11
  • 82
  • 149
0__
  • 66,707
  • 21
  • 171
  • 266
6

The one-line-ish use of BasicIO or ProcessLogger is appealing.

scala> val sb = new StringBuffer
sb: StringBuffer = 

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

scala> sb
res1: StringBuffer = ...

or

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 = ...

Depending on what you mean by capture, perhaps you're interested in output unless the exit code is nonzero. In that case, handle the exception.

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
som-snytt
  • 39,429
  • 2
  • 47
  • 129
  • I think it's not at all safe to assume that the error message will always be exactly `Nonzero exit value: (...)`... – ValarDohaeris May 11 '13 at 22:22
  • That's a flaw in the API, to be sure, but fortunately it's open source and you'll notice if they ever try to change it and you'll say, Hey, people rely on that message! and they'll revert it. I'm trying to remember if I've ever relied on non-zero exit of a process being a three instead of two or seventeen. – som-snytt May 11 '13 at 22:55
  • By "not at all safe" you probably meant "not entirely safe". Of course, the error doesn't have to be exactly that, but only include the pattern. – som-snytt May 11 '13 at 22:58
  • @Alex `import sys.process._` – som-snytt Mar 03 '14 at 08:01
  • All three examples were helpful. Trying to internalize ("Dilbert: optimizing hard disk .. *now* ..) – WestCoastProjects Jun 05 '15 at 04:02
2

The response provided by 'Alex Cruise' in your link is fairly concise, barring poorer performance.

You could extend sys.process.ProcessLogger to manage the

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

internally, with getters for the out.reverse and err.reverse results.

Richard Sitze
  • 8,262
  • 3
  • 36
  • 48
0

Here's a really simple Scala wrapper that allows you to retrieve stdout, stderr and exit code.

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)
  }
}
Prometheus
  • 523
  • 7
  • 19
  • beware: this solution does not capture the formatting of the output (i.e., newlines are dropped) – Sim Aug 11 '20 at 11:22
0

I combined these and came up with this. The expected RC is there because I have a program I need to run in one project that returns 1 when it works. This does depend on the text of the Exception, but it will still do something reasonable it that doesn't match.

  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)
        }
    }
  }
Bday
  • 1,005
  • 7
  • 13