8

我有一个外部进程,我想将其视为String=>String. 给定一行输入,它将以单行输出响应。看来我应该使用 scala.sys.process,这显然是一个优雅的库,可以让许多 shell 操作在 scala 中轻松访问。但是,我不知道如何执行这个简单的用例。

如果我在进程的标准输入中写入一行,它会将结果打印在一行中。如何使用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因为有一个重要的初始化步骤。

4

5 回答 5

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 => Unit - 函数)。主要只是一个示例实现 - 调用 python ;)

于 2013-02-11T20:18:00.377 回答
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是一种更好的方法。

于 2013-02-06T18:12:10.550 回答
1

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

于 2013-01-30T18:09:20.883 回答
1

使用 Akka 演员怎么样。参与者可以有状态,因此可以引用一个打开的程序(在一个线程中)。您可以向该参与者发送消息。

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

让程序打开并准备好接收命令听起来像是一个接收消息的参与者。

于 2013-02-08T07:32:24.650 回答
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-> ProcessBuilder-> ProcessBuilderWithStringInput,而 Scala 只会自动应用一次转换。

于 2013-01-30T07:37:24.027 回答