我有一个外部流程,我想将其视为一个
来自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
时都不会重新加载进程,因为有一个重要的初始化步骤。
答案 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只会自动应用一次转换。