如何将字符串写入Scala Process?

时间:2017-01-08 14:53:41

标签: scala process io inputstream outputstream

我开始运行Scala进程。

    val dir = "/path/to/working/dir/"
    val stockfish = Process(Seq("wine", dir + "stockfish_8_x32.exe"))
    val logger = ProcessLogger(printf("Stdout: %s%n",  _))
    val stockfishProcess = stockfish.run(logger, connectInput = true)

该过程从标准IO(控制台)读取和写入。如果已经启动过该字符串命令,该怎么办?

Scala进程API具有ProcessBuilder,而ProcessBuilder又有许多有用的方法。但ProcessBuilder在之前进程开始组成复杂的shell命令。 Scala还有ProcessIO来处理输入或输出。我也不需要它。我只需要向我的流程发送消息。

在Java中我会做这样的事情。

        String dir = "/path/to/working/dir/";
        ProcessBuilder builder = new ProcessBuilder("wine", dir + "stockfish_8_x32.exe");
        Process process = builder.start();

        OutputStream stdin = process.getOutputStream();
        InputStream stdout = process.getInputStream();

        BufferedReader reader = new BufferedReader(new InputStreamReader(stdout));
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stdin));

        new Thread(() -> {
            try {
                String line;
                while ((line = reader.readLine()) != null) {
                    System.out.println("Stdout: " + line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();

        Thread.sleep(5000); // it's just for example
        writer.write("quit");  // send to the process command to stop working
        writer.newLine();
        writer.flush();

效果很好。我启动我的进程,从中获取InputStream和OutputStream,并使用流与进程交互。

似乎Scala Process trait没有提供写入它的方法。进程运行后,ProcessBuilder无用。而ProcessIO只是用于IO捕获和处理。

有没有办法写Scala运行过程?

更新

我不知道如何使用ProcessIO将字符串传递给正在运行的进程。 我做了以下。

import scala.io.Source
import scala.sys.process._

object Sample extends App {

   def out = (output: java.io.OutputStream) => {
      output.flush()
      output.close()
   }

   def in = (input: java.io.InputStream) => {
      println("Stdout: " + Source.fromInputStream(input).mkString)
      input.close()
   }

   def go = {
      val dir = "/path/to/working/dir/"
      val stockfishSeq = Seq("wine", dir + "/stockfish_8_x32.exe")
      val pio = new ProcessIO(out, in, err => {})
      val stockfish = Process(stockfishSeq)
      stockfish.run(pio)

      Thread.sleep(5000)
      System.out.write("quit\n".getBytes)
      pio.writeInput(System.out) // "writeInput" is function "out" which I have passed to conforming ProcessIO instance. I can invoke it from here. It takes OutputStream but where can I obtain it? Here I just pass System.out for example.
   }
   go
 }

当然它不起作用,我无法理解如何实现上面的Java代码段中的功能。很高兴有Scala代码的建议或片段清除我的问题。

4 个答案:

答案 0 :(得分:1)

我认为围绕Scala进程的文档(特别是ProcessIO的用法和语义)可以使用一些改进。我第一次尝试使用这个API时,我发现它非常混乱,并且需要一些试验和错误才能使我的子进程i / o正常工作。

我认为看到一个简单的例子可能就是你真正需要的。我会做一些非常简单的事情:调用bc作为子进程来进行一些简单的计算,然后将答案打印到我的标准输出。我的目标是做这样的事情(但是来自Scala,而不是来自我的shell):

$ printf "1+2\n3+4\n" | bc
3
7

以下是我在Scala中的表现:

import scala.io.Source
import scala.sys.process._

object SimpleProcessExample extends App {

  def out = (output: java.io.OutputStream) => {
    output.flush()
    output.close()
  }

  def in = (input: java.io.InputStream) => {
    println("Stdout: " + Source.fromInputStream(input).mkString)
    input.close()
  }

  // limit scope of any temporary variables
  locally {
    val calcCommand = "bc"
    // strings are implicitly converted to ProcessBuilder
    // via scala.sys.process.ProcessImplicits.stringToProcess(_)
    val calcProc = calcCommand.run(new ProcessIO(
      // Handle subprocess's stdin
      // (which we write via an OutputStream)
      in => {
        val writer = new java.io.PrintWriter(in)
        writer.println("1 + 2")
        writer.println("3 + 4")
        writer.close()
      },
      // Handle subprocess's stdout
      // (which we read via an InputStream)
      out => {
        val src = scala.io.Source.fromInputStream(out)
        for (line <- src.getLines()) {
          println("Answer: " + line)
        }
        src.close()
      },
      // We don't want to use stderr, so just close it.
      _.close()
    ))

    // Using ProcessBuilder.run() will automatically launch
    // a new thread for the input/output routines passed to ProcessIO.
    // We just need to wait for it to finish.

    val code = calcProc.exitValue()

    println(s"Subprocess exited with code $code.")

  }
}

请注意,您实际上并未直接调用ProcessIO对象的任何方法,因为它们会被ProcessBuilder自动调用。

结果如下:

$ scala SimpleProcessExample
Answer: 3
Answer: 7
Subprocess exited with code 0.

如果您希望输入和输出处理程序与子进程之间进行交互,则可以使用标准线程通信工具(例如,两者都关闭BlockingQueue的实例)。

答案 1 :(得分:0)

我实际上没有尝试过这个,但documentation表示你可以使用ProcessIO的实例来处理Process的输入和输出,其方式与在Java中的方式类似。

答案 2 :(得分:0)

以下是从流程获取输入和输出流的示例,您可以在流程开始后写入和读取:

object demo {
  import scala.sys.process._

  def getIO = {
    // create piped streams that can attach to process streams:
    val procInput = new java.io.PipedOutputStream()
    val procOutput = new java.io.PipedInputStream()
    val io = new ProcessIO(
      // attach to the process's internal input stream
      { in =>
        val istream = new java.io.PipedInputStream(procInput)
        val buf = Array.fill(100)(0.toByte)
        var br = 0
        while (br >= 0) {
          br = istream.read(buf)
          if (br > 0) { in.write(buf, 0, br) }
        }
        in.close()
      },
      // attach to the process's internal output stream
      { out =>
        val ostream = new java.io.PipedOutputStream(procOutput)
        val buf = Array.fill(100)(0.toByte)
        var br = 0
        while (br >= 0) {
          br = out.read(buf)
          if (br > 0) { ostream.write(buf, 0, br) }
        }
        out.close()
      },
      // ignore stderr
      { err => () }
    )
    // run the command with the IO object:
    val cmd = List("awk", "{ print $1 + $2 }")
    val proc = cmd.run(io)

    // wrap the raw streams in formatted IO objects:
    val procO = new java.io.BufferedReader(new java.io.InputStreamReader(procOutput))
    val procI = new java.io.PrintWriter(procInput, true)
    (procI, procO)
  }
}

这是使用输入和输出对象的简短示例。请注意,在关闭输入流/对象之前,很难保证进程将接收它的输入,因为所有内容都是管道,缓冲等。

scala> :load /home/eje/scala/input2proc.scala
Loading /home/eje/scala/input2proc.scala...
defined module demo

scala> val (procI, procO) = demo.getIO
procI: java.io.PrintWriter = java.io.PrintWriter@7e809b79
procO: java.io.BufferedReader = java.io.BufferedReader@5cc126dc

scala> procI.println("1 2")

scala> procI.println("3 4")

scala> procI.println("5 6")

scala> procI.close()

scala> procO.readLine
res4: String = 3

scala> procO.readLine
res5: String = 7

scala> procO.readLine
res6: String = 11

scala> 

通常,如果在同一个线程中同时管理输入和输出,则可能存在死锁,因为读取或写入都可能阻塞等待另一个。在自己的线程中运行输入逻辑和输出逻辑是最安全的。考虑到这些线程问题,也可以将输入和输出逻辑直接放入定义{ in => ... }{ out => ... }中,因为它们都在不同的线程中自动运行

答案 3 :(得分:0)

var outPutStream: Option[OutputStream] = None
val io = new ProcessIO(
  { outputStream =>
    outPutStream = Some(outputStream)
  },
  Source.fromInputStream(_).getLines().foreach(println),
  Source.fromInputStream(_).getLines().foreach(println)
)
command run io

val out = outPutStream.get
out.write("test" getBytes())

你可以用同样的方式得到一个 InputStream。