sys.process将进程包装为函数

时间:2013-01-30 01:47:15

标签: scala

我有一个外部流程,我想将其视为一个 来自String=>String的功能。给定一行输入,它将以单行输出响应。好像我应该用 scala.sys.process,这显然是一个优雅的库,可以创建很多 从scala中可以轻松访问shell操作。但是,我 无法弄清楚如何执行这个简单的用例。

如果我向进程'stdin写一行,它会打印结果 在一条线上。我如何使用sys.process来创建包装器 可以交互使用这个过程吗?例如,如果我有一个 ProcessWrapper的实现,这是一个程序,它的输出:

// abstract definition
class ProcessWrapper(executable: String) {
  def apply(line: String): String
}

// program using an implementation
val process = new ProcessWrapper("cat -b")
println(process("foo"))
println(process("bar"))
println(process("baz"))

输出:

 1  foo
 2  bar
 3  baz

重要的是,每次调用process时都不会重新加载进程,因为有一个重要的初始化步骤。

5 个答案:

答案 0 :(得分:3)

所以 - 在我的评论之后 - 这将是我的解决方案

import java.io.BufferedReader
import java.io.File
import java.io.InputStream
import java.io.InputStreamReader

import scala.annotation.tailrec

class ProcessWrapper(cmdLine: String, lineListenerOut: String => Unit, lineListenerErr: String => Unit,
                     finishHandler: => Unit,
                     lineMode: Boolean = true, envp: Array[String] = null, dir: File = null) {

  class StreamRunnable(val stream: InputStream, listener: String => Unit) extends Runnable {
    def run() {
      try {
        val in = new BufferedReader(new InputStreamReader(this.stream));
        @tailrec
        def readLines {
          val line = in.readLine
          if (line != null) {
            listener(line)
            readLines
          }
        }
        readLines
      }
      finally {
        this.stream.close
        finishHandler
      }
    }
  }
  val process = Runtime.getRuntime().exec(cmdLine, envp, dir);
  val outThread = new Thread(new StreamRunnable(process.getInputStream, lineListenerOut), "StreamHandlerOut")
  val errThread = new Thread(new StreamRunnable(process.getErrorStream, lineListenerErr), "StreamHandlerErr")
  val sendToProcess = process.getOutputStream
  outThread.start
  errThread.start

  def apply(txt: String) {
    sendToProcess.write(txt.getBytes)
    if (lineMode)
      sendToProcess.write('\n')
    sendToProcess.flush
  }

}

object ProcessWrapper {
  def main(args: Array[String]) {
    val process = new ProcessWrapper("python -i", txt => println("py> " + txt),
      err => System.err.println("py err> " + err), System.exit(0))
    while (true) {
      process(readLine)
    }
  }
}

主要部分是StreamRunnable,其中进程在线程中读取,接收的行传递给“LineListener”(简单的String =>单元 - 函数)。 main只是一个示例实现 - 调用python;)

答案 1 :(得分:2)

我不确定,但你想要那样的事情吗?

  case class ProcessWrapper(executable: String) {
    import java.io.ByteArrayOutputStream
    import scala.concurrent.duration.Duration
    import java.util.concurrent.TimeUnit

    lazy val process = sys.runtime.exec(executable)

    def apply(line: String, blockedRead: Boolean = true): String = {
      process.getOutputStream().write(line.getBytes())
      process.getOutputStream().flush()
      val r = new ByteArrayOutputStream
      if (blockedRead) {
        r.write(process.getInputStream().read())
      }
      while (process.getInputStream().available() > 0) {
        r.write(process.getInputStream().read())
      }
      r.toString()
    }

    def close() = process.destroy()
  }

  val process = ProcessWrapper("cat -b")

  println(process("foo\n"))
  println(process("bar\n"))
  println(process("baz\n"))
  println(process("buz\n"))
  println(process("puz\n"))

  process.close

结果:

 1    foo

 2    bar

 3    baz

 4    buz

 5    puz

我认为PlayCLI是一种更好的方式。

答案 2 :(得分:1)

http://blog.greweb.fr/2013/01/playcli-play-iteratees-unix-pipe/今天遇到了这个,看起来与你想要的完全一样

答案 3 :(得分:1)

如何使用Akka演员。 actor可以具有状态,因此可以引用打开的程序(在线程中)。您可以向该演员发送消息。

ProcessWrapper可能是一个类型化的actor本身,或者只是将函数调用转换为actor调用的东西。如果您只有'process'作为方法名称,那么wrapper ! "message"就足够了。

让程序打开并准备接收命令听起来像是接收消息的actor。

答案 4 :(得分:0)

编辑:可能我的要求错了。您想要将多行发送到同一进程。使用以下解决方案是不可能的。

一种可能性是向ProcessBuilder添加一个允许从字符串中获取输入的扩展方法:

implicit class ProcessBuilderWithStringInput(val builder: ProcessBuilder) extends AnyVal {
  // TODO: could use an implicit for the character set
  def #<<(s: String) = builder.#<(new ByteArrayInputStream(s.getBytes))
}

您现在可以使用以下方法:

scala> ("bc":ProcessBuilder).#<<("3 + 4\n").!!
res9: String = 
"7
"

请注意,类型注释是必要的,因为我们需要两次转换(String - &gt; ProcessBuilder - &gt; ProcessBuilderWithStringInput,而Scala只会自动应用一次转换。