如何发出立即产生无限输出和 return 的命令

How to issue a command that produces infinite output and return immediately

当我编写以下代码时(在菊石中但我认为这不重要)

("tail -f toTail.txt" lineStream) foreach(println(_)),程序按照预期给我最后一行但随后挂起,即使我在文件中写了更多内容,也没有任何结果。

API 如何支持具有无限输出的进程?

我试着写val myStream = ("tail -f toTail.txt" lineStream_!) 但它仍然没有return写掉

这是 scala 文档所说的内容:

lineStream: returns immediately like run, and the output being generated is provided through a Stream[String]. Getting the next element of that Stream may block until it becomes available.

所以我不明白为什么它会阻塞

顺便说一句,我和 Ammonite 的行为完全一样 API

如果再次输入 %%("tail", "-f", "toTail.txt") 该方法只是挂起,不会立即 return。

ProcessBuilder 没有问题(至少不是源于您的用例的问题)。来自 ProcessBuilder documentation:

Starting Processes

To execute all external commands associated with a ProcessBuilder, one may use one of four groups of methods. Each of these methods have various overloads and variations to enable further control over the I/O. These methods are:

  • run: the most general method, it returns a scala.sys.process.Process immediately, and the external command executes concurrently.
  • !: blocks until all external commands exit, and returns the exit code of the last one in the chain of execution.
  • !!: blocks until all external commands exit, and returns a String with the output generated.
  • lineStream: returns immediately like run, and the output being generated is provided through a Stream[String]. Getting the next element of that Stream may block until it becomes available. This method will throw an exception if the return code is different than zero -- if this is not desired, use the lineStream_! method.

文档明确指出 lineStream 可能会阻塞,直到下一行可用。由于 tail -f 的本质是无限的行流 lineBreak 程序将阻塞等待下一行的出现。

以下假设我有一个文件:/Users/user/tmp/sample.txt,它的内容是:

boom
bar
cat

为什么lineStream_!没有错

import scala.language.postfixOps
import scala.sys.process._

object ProcessBuilder extends App {

  val myStream: Stream[String] = ("tail /Users/user/tmp/sample.txt" lineStream_!)
  println("I'm after tail!")
  myStream.filter(_ != null).foreach(println)
  println("Finished")
  System.exit(0)

}

输出:

I'm after tail!
boom
bar
cat
Finished

所以你看到 lineStream_! return 立即编辑了。因为命令的性质是有限的。

如何从产生无限输出的命令中立即return:

让我们用 tail -f 试试这个。您需要对流程进行更多控制。同样,正如文档所述:

If one desires full control over input and output, then a scala.sys.process.ProcessIO can be used with run.

举个例子:

import java.io.{BufferedReader, InputStreamReader}

import scala.language.postfixOps
import scala.sys.process._

object ProcessBuilder extends App {

  var reader: BufferedReader = _
  try {

    var myStream: Stream[String] = Stream.empty
    val processIO = new ProcessIO(
      (os: java.io.OutputStream) => ??? /* Send things to the process here */,
      (in: java.io.InputStream) => {

        reader = new BufferedReader(new InputStreamReader(in))
        myStream = Stream.continually(reader.readLine()).takeWhile(_ != "ff")
      },
      (in: java.io.InputStream) => ???,
      true
    )
    "tail -f /Users/user/tmp/sample.txt".run(processIO)
    println("I'm after the tail command...")

    Thread.sleep(2000)
    println("Such computation performed while tail was active!")

    Thread.sleep(2000)
    println("Such computation performed while tail was active again!")

    println(
      s"Captured these lines while computing: ${myStream.print(System.lineSeparator())}")

    Thread.sleep(2000)
    println("Another computation!")

  } finally {
    Option(reader).foreach(_.close())
  }
  println("Finished")
  System.exit(0)

}

输出:

I'm after the tail command...
Such computation performed while tail was active!
Such computation performed while tail was active again!
boom
bar
cat

它仍然 return 立即,现在它只是挂在那里等待更多输入。如果我从 tmp 目录执行 echo 'fff' >> sample.txt,程序输出:

Another computation!
Finished

您现在可以在发出 tail -f 命令后执行您想要的任何计算,并且可以根据您传递给 takeWhile 方法(或其他方法)的条件终止它关闭输入流)。

有关 ProcessIO 的更多详细信息,请查看 documentation here

我认为,这与您向文件中添加数据的方式有关,与 ProcessBuilder 无关。例如,如果您在编辑器中使用它来添加数据,它会在您每次保存时使用不同的 inode 重写整个文件,而 tail 没有检测到这一点。

尝试 tail -F 而不是 tail -f,应该可以。