我正在尝试编写一个包装另一个命令的Groovy脚本,并且遇到了stdout / stderr命令的问题。我的脚本如下:
#!/usr/bin/env groovy
synchronized def output = ""
def process = "qrsh ${args.join(' ')}".execute()
def outTh = Thread.start {
process.in.eachLine {
output += it
System.out.println "out: $it"
}
}
def errTh = Thread.start {
process.err.eachLine {
output += it
System.err.println "err: $it"
}
}
outTh.join()
errTh.join()
process.waitFor()
System.exit(process.exitValue())
我的问题是输出没有以正确的顺序出现在终端上。下面是包装器的输出。
[<cwd>] wrap.groovy -cwd -V -now n -b y -verbose ant target
waiting for interactive job to be scheduled ...
Your interactive job 2831303 has been successfully scheduled.
Establishing builtin session to host <host> ...
Buildfile: build.xml
BUILD FAILED
Target "target" does not exist in the project "null".
Total time: 0 seconds
Your job 2831303 ("wrap.groovy") has been submitted
下面是解包的命令输出。
[<cwd>] qrsh -cwd -V -now n -b y -verbose ant target
Your job 2831304 ("ant") has been submitted
waiting for interactive job to be scheduled ...
Your interactive job 2831303 has been successfully scheduled.
Establishing builtin session to host host ...
Buildfile: build.xml
BUILD FAILED
Target "target" does not exist in the project "null".
Total time: 0 seconds
为什么“你的工作已被提交”消息显示为一个演员阵容中的第一行和另一个演员阵容中的最后一行?我猜它与Java库有关,而不是Groovy。
答案 0 :(得分:5)
这是因为缓冲。读取stdout和stderr的线程在子进程写入时不会处理输出。相反,两个流都是缓冲的,因此除非孩子刷新流,否则您的进程将看不到任何。
当数据正在运行时,哪个线程首先得到CPU?没有办法说出来。即使stderr的数据在stdout之前几毫秒到达,如果stdout线程现在有CPU,它将首先得到它的数据。
您可以做的是使用Java NIO(通道)和单个线程,并首先处理来自stderr的所有输出,但仍然不能保证订单被保留。由于子进程和父进程之间的缓冲,在看到另一个进程的单个字节之前,您可以从一个流中获取4KB的文本。
不幸的是,没有跨平台解决方案,因为Java没有API将两个流合并为一个。在Unix上,您可以使用sh -c cmd 2>&1
运行命令。这会将stderr重定向到stdout。在父进程中,您可以只读取stdout并忽略stderr。
OS X也是如此(因为它基于Unix)。在Windows上,您可以安装Perl或类似工具来运行该过程;这可以让你弄乱文件描述符。
PS:祈祷args
永远不会包含空格。 String.execute()
是运行流程的一种非常糟糕的方式;请改用java.lang.ProcessBuilder
。
答案 1 :(得分:1)
尝试在执行println后输入System.out.flush。如果我是对的,消息将以不同的顺序出现,因为System.out正在缓冲。