5

In Python, if I want to call an external command as a subprocess, I do the following:

from subprocess import Popen, PIPE
cmd = ['cat', '-be']
out, err = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE).communicate("some input")

What's the standard way to do the same in Scala? Using Java's ProcessBuilder I came up with the following, but it's pretty ugly:

def communicate(cmd: List[String], input: Option[String] = None): (String, String) = {

    val command = new java.util.ArrayList[String]()
    cmd.foreach(command.add(_))

    val builder = new ProcessBuilder(command)
    val process = builder.start()

    val stdinWriter = new java.io.PrintWriter((new java.io.OutputStreamWriter(new java.io.BufferedOutputStream(process.getOutputStream()))), true);
    val stdoutReader = new java.io.BufferedReader(new java.io.InputStreamReader(process.getInputStream()))
    val stderrReader = new java.io.BufferedReader(new java.io.InputStreamReader(process.getErrorStream()))

    input.foreach(stdinWriter.write(_))
    stdinWriter.close()

    def read(reader: java.io.BufferedReader): String = {
        val out = new ListBuffer[String]
        var line: String = reader.readLine()
        while (line != null) {
            out += line
            line = reader.readLine()
        }
        return out.result.mkString("\n")
    }

    val stdout = read(stdoutReader)
    val stderr = read(stderrReader)

    stdoutReader.close()
    stderrReader.close()

    return (stdout, stderr)

}

val (catout, caterr) = communicate(List("cat", "-be"), Some("some input"))
val (pwdout, pwderr) = communicate(List("pwd"))

Is there a better alternative built into Scala already?

dhg
  • 52,383
  • 8
  • 123
  • 144

3 Answers3

11

The answer here How does the “scala.sys.process” from Scala 2.9 work? shows how to use the new Scala 2.9 scala.sys.process.Process. If you don't use 2.9 you can use the process part of sbt; which the Scala process originates from.

Community
  • 1
  • 1
thoredge
  • 12,237
  • 1
  • 40
  • 55
1

Using the OS-Lib library, your code can be written as the following Scala:

@ val res = os.proc("cat", "-be").call(stdin = "some input")
res: os.CommandResult = CommandResult(0, List(Left(     1   some input)))

@ res.out.string
res3: String = "     1\tsome input"

@ res.err.string
res4: String = ""
Li Haoyi
  • 15,330
  • 17
  • 80
  • 137
0

Hav you looked at Apache commons exec http://commons.apache.org/exec/tutorial.html?

I've used it successfully from Scala a few years ago. Digged up some code:

def quickRun(command: String, allowedExitCodes: Array[Int]): String = {
  val executor = new DefaultExecutor()
  val outputStream = new ByteArrayOutputStream()
  val errorStream = new ByteArrayOutputStream()

  val pumpStreamHandler = new PumpStreamHandler(outputStream, errorStream)
  executor.setStreamHandler(pumpStreamHandler)
  executor.setExitValues(allowedExitCodes)

  lazy val errorMsg = "Couldn't execute command: \"" + command + "\", errorStream: " + errorStream.toString()
  try {
    val exitValue = executor.execute(CommandLine.parse(command))
    if (executor.isFailure(exitValue)) {
      log.error(errorMsg)
      throw new CommandLineException(errorMsg)
    } else {
      return outputStream.toString()
    }
  } catch {
    case e: ExecuteException =>
      log.error(errorMsg)
      throw new CommandLineException(errorMsg)
    case e: IOException => throw new CommandLineException(errorMsg)
  }
}

But scala.sys.process looks better, I think you should use that if possible…

Viktor Hedefalk
  • 3,572
  • 3
  • 33
  • 48