假设我们有以下Scala(2.12)。它使用ProcessBuilder执行非常简单的命令集(a sleep
,后跟echo
)。该程序还捕获stdout和stderr,并将所有行(带有那些前缀)打印到Scala进程本身的stdout或stderr。
import scala.sys.process._
import scala.language.postfixOps
object BackgroundProcessRedirect {
def main(args: Array[String]) = {
val output = "sleep 5" #&& s"echo myoutput" lineStream_! ProcessLogger(line => System.err.println(s"stderr: $line"))
output.foreach(line => System.out.println(s"stdout: $line"))
}
}
Run Code Online (Sandbox Code Playgroud)
在zsh
5.6.2或GNU bash
4.4.23(1)中的OS X上执行时,除非附加了stdin,否则由于tty输出,该过程将被挂起。尽管事实上命令(sleep
和echo
)都不应该尝试从stdin读取,但仍会发生这种情况。
# first, compile
scalac BackgroundProcessRedirect.scala
# now, run as a background process, redirecting stdout and stderr to files
scala BackgroundProcessRedirect >/tmp/scala.out 2>/tmp/scala.err &
# after 5 seconds, the process is suspended, as in the following jobs output
[1] + <PID> suspended (tty output) scala BackgroundProcessRedirect > /tmp/scala.out 2> /tmp/scala.err
# now, foreground the process; it will complete immediately
fg
# the output file contents are as expected
cat /tmp/scala.out
stdout: myoutput
# run the same thing again, but this time redirect stdin from /dev/null
scala BackgroundProcessRedirect </dev/null >/tmp/scala.out 2>/tmp/scala.err &
# now, the process completes normally after 5 seconds (not suspended), and the file contents are as expected
cat /tmp/scala.out
stdout: myoutput
Run Code Online (Sandbox Code Playgroud)
如果程序只是从前台开始运行,那么它也不会挂起。任何想法可能是什么原因造成的?
stty -tostop
在启动的终端中运行scala
,或者在调用这些相同命令然后由调用的新脚本文件中运行,scala
对此行为没有任何影响。
尽管没有命令(sleep或echo)都不应尝试从stdin读取,但还是会发生这种情况
请注意,该消息显示为“ tty 输出 ”,而不是“ tty 输入 ”。因此,它抱怨该过程产生输出,而不是它从stdin读取。这很奇怪,原因有两个:
通常仅在该过程使用tty功能(而不仅仅是写入标准流)时才停止输出(例如,写入屏幕上的特定位置或通常需要诅咒或类似功能的任何内容)。
您的程序不会执行此操作,但是显然scala
二进制会执行¹。尝试使用运行任何Scala程序时scala className&
,至少我可以重现您的问题,但是当我改用它时,它就消失了java -cp /usr/share/scala/lib/scala-library.jar:. className&
。
因此,我的结论是,scala
与tty的交互方式会破坏后台进程,java
而应使用替代方法。
¹查看后/usr/bin/scala
,似乎正在调用stty
保存和恢复终端设置。显然,如果该进程在后台运行(因此未正确连接到tty)是行不通的,这就是该进程被挂起的原因。