我正在尝试编写一个包装另一个命令的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有关。

最佳答案

这是由于缓冲。读取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代替。

10-02 03:35
查看更多