如何将字符串写入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 => ...
和 out => ...
,因为它们都自动在单独的线程中运行
【讨论】:
谢谢你。我需要一个管道流示例。我必须将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?的主要内容,如果未能解决你的问题,请参考以下文章
如何将字符串解析为 child_process.spawn 的适当参数?