如何将字符串写入Scala Process?

Posted

技术标签:

【中文标题】如何将字符串写入Scala Process?【英文标题】:How to write a string to Scala Process? 【发布时间】:2017-05-22 21:17:34 【问题描述】:

我开始并运行了一个 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 进程开始编写复杂的 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 sn-p 中的功能。如果有 Scala 代码的建议或 sn-p 来解决我的问题,那就太好了。

【问题讨论】:

我想我误解了你的问题,所以我删除了我的答案。您可能应该指定您正在使用哪个版本的 Scala(我认为与子流程相关的 API 最近发生了变化)。我很困惑为什么 ProcessIO 不适合你。看起来您的 Java 代码只是使用标准输入/标准输出,这也可以使用 ProcessIO。你能澄清一下吗? @DaoWen 是的,在 Java 中很简单。 Java“Process”类提供了返回我可以使用的 IO 流的方法。 Scala "Process" trait 只有三个方法:destroy、exitValue 和 isAlive。 ProcessIO 类具有函数参数“writeInput”,可以直接传递和调用。我猜它只是输入事件的一种侦听器,但是如何发起这个“事件”呢?我使用最后一个 Scala 版本 - 2.12.1。 【参考方案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 的实例上)。

【讨论】:

谢谢你的例子。很有说明性。但是,在您的 sn-p 中,您知道在创建流程时您要计算的确切内容,并且将所有逻辑移至“in”方法没有问题。 想象一下您的计算器正在等待用户输入一些表达式,然后计算器应该处理该表达式。在“in”函数中将没有什么可写的。您不知道用户的具体类型。如果您有 OutputStream 实例,就像在 Java 中一样,您可以在用户键入内容时随时写入它。但你没有。这是我的问题。我可以启动进程,但在其工作期间无法写入。【参考方案2】:

这是一个从进程获取输入和输出流的示例,您可以在进程启动后对其进行写入和读取:

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 =&gt; ... out =&gt; ... ,因为它们都自动在单独的线程中运行

【讨论】:

谢谢你。我需要一个管道流示例。我必须将ostream.close 添加到ProcessIO out 函数中,否则我会收到IOException: Write end dead 在进一步的测试中,有时我会收到Pipe not connected 错误。如果我在out 函数有机会运行之前尝试阅读procO,我很确定会发生这种情况。我通过创建 PipedOutputStream 并提前连接它解决了这个问题:val procOutput = new java.io.PipedInputStream(); val pop = new java.io.PipedOutputStream(procOutput) 然后在out 函数中使用pop 而不是ostream【参考方案3】:

我实际上并没有尝试过,但documentation 说您可以使用ProcessIO 的实例来处理进程的输入和输出,其方式类似于您在Java 中所做的。

【讨论】:

【参考方案4】:
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。

【讨论】:

以上是关于如何将字符串写入Scala Process?的主要内容,如果未能解决你的问题,请参考以下文章

delphi 如何写入Excel

php怎样把一个数组写入一个文件

MongoDB 将Json数据直接写入MongoDB的方法

如何将字符串解析为 child_process.spawn 的适当参数?

如何使用 spark/scala 将 json 字符串格式化为 MongoDB 文档样式?

Java 或 Scala。如何将 \x22 等字符转换为字符串