Groovy执行shell命令

218

Groovy 为 String 添加了 execute 方法,使得执行 shell 命令变得相当容易;

println "ls".execute().text

但如果发生错误,则没有任何输出结果。有没有一种简单的方法可以同时获取标准错误标准输出(除了创建大量代码来创建两个线程来读取两个输入流,使用父流等待它们完成,然后将字符串转换回文本之外)

有这样的东西会很好;

 def x = shellDo("ls /tmp/NoFile")
 println "out: ${x.out} err:${x.err}"

这个链接很有用。演示了如何使用cURL运行shell命令。 - Aniket Thakur
7个回答

286

好的,我自己解决了。

def sout = new StringBuilder(), serr = new StringBuilder()
def proc = 'ls /badDir'.execute()
proc.consumeProcessOutput(sout, serr)
proc.waitForOrKill(1000)
println "out> $sout\nerr> $serr"

显示:

out> err> ls: 无法访问 / badDir:没有那个文件或目录


18
如果需要为此进程设置环境变量,请确保在shell中运行命令。例如,使用环境变量运行Perforce命令:envVars = ["P4PORT=p4server:2222", "P4USER=user", "P4PASSWD=pass", "P4CLIENT=p4workspace"]; workDir = new File("path"); cmd = "bash -c \"p4 change -o 1234\""; proc = cmd.execute(envVars, workDir); - Noam Manos
1
@paul_sns 这段话与OP问题无关,但我认为现代JVM可以很好地处理不受限制的同步。因此,在线程或堆栈受限的情况下,StringBuffer不太可能降低性能。 - Pavel Grushetzky
5
文档称我们应该使用waitForProcessOutput() - "等待输出被完全消耗,调用waitForProcessOutput()"。来源:http://docs.groovy-lang.org/latest/html/groovy-jdk/java/lang/Process.html#consumeProcessOutput(java.io.OutputStream,%20java.io.OutputStream) - Srikanth
4
@srikanth,waitForProcess() 的输出文档还说:“如果您不关心标准或错误输出,只想让进程静默运行,请使用此方法”-我需要输出。 - Bob Herrmann
即使在waitForOrKill之后,sout和serr可能仍然不可用。使用assert而不是println进行测试。文档说:“为此,启动了两个线程,因此此方法将立即返回。即使调用了waitFor(),线程也不会join()。要等待输出完全被消耗,请调用waitForProcessOutput()。” - solstice333

66

"ls".execute() 返回一个 Process 对象,这就是为什么 "ls".execute().text 起作用的原因。你应该能够仅通过读取错误流来确定是否有任何错误。

Process 上还有一个额外的方法,允许你传递一个 StringBuffer 来检索文本:consumeProcessErrorStream(StringBuffer error)

示例:

def proc = "ls".execute()
def b = new StringBuffer()
proc.consumeProcessErrorStream(b)

println proc.text
println b.toString()

它与Bourn Again Shell脚本不兼容!#!/bin/bash, - Rashmi Jain
1
如果使用bash脚本,你可能会在命令中调用bash:"/bin/bash script".execute()。 - Niels Bech Nielsen

40

我觉得这种表达更符合惯用语:

def proc = "ls foo.txt doesnotexist.txt".execute()
assert proc.in.text == "foo.txt\n"
assert proc.err.text == "ls: doesnotexist.txt: No such file or directory\n"

正如另一篇帖子所提到的,这些都是阻塞调用,但由于我们想要处理输出,这可能是必要的。


38
// a wrapper closure around executing a string                                  
// can take either a string or a list of strings (for arguments with spaces)    
// prints all output, complains and halts on error                              
def runCommand = { strList ->
  assert ( strList instanceof String ||
           ( strList instanceof List && strList.each{ it instanceof String } ) \
)
  def proc = strList.execute()
  proc.in.eachLine { line -> println line }
  proc.out.close()
  proc.waitFor()

  print "[INFO] ( "
  if(strList instanceof List) {
    strList.each { print "${it} " }
  } else {
    print strList
  }
  println " )"

  if (proc.exitValue()) {
    println "gave the following error: "
    println "[ERROR] ${proc.getErrorStream()}"
  }
  assert !proc.exitValue()
}

11
这个功能会逐步显示输出内容,随着输出内容的生成而更新。对于长时间运行的进程来说,这非常重要。 - samarjit samanta
6
要使用这个解决方案,请输入以下命令:runCommand("echo HELLO WORLD") - Miron Veryanskiy
@mholm815,我们如何在管道本身中批准所需的脚本? - RNK
@RNK: 他离开了 大约在2017年左右:"最后一次出现是6年前"。 - Peter Mortensen

27

在之前的回答中,再添加一条重要信息:

对于一个进程

def proc = command.execute();

始终尝试使用

def outputStream = new StringBuffer();
proc.waitForProcessOutput(outputStream, System.err)
//proc.waitForProcessOutput(System.out, System.err)

而不是

def output = proc.in.text;

在Groovy中执行命令时,要捕获输出结果,因为后者是阻塞调用(关于原因的SO问题)。


8
def exec = { encoding, execPath, execStr, execCommands ->

    def outputCatcher = new ByteArrayOutputStream()
    def errorCatcher = new ByteArrayOutputStream()

    def proc = execStr.execute(null, new File(execPath))
    def inputCatcher = proc.outputStream

    execCommands.each { cm ->
        inputCatcher.write(cm.getBytes(encoding))
        inputCatcher.flush()
    }

    proc.consumeProcessOutput(outputCatcher, errorCatcher)
    proc.waitFor()

    return [new String(outputCatcher.toByteArray(), encoding), new String(errorCatcher.toByteArray(), encoding)]
}

def out = exec("cp866", "C:\\Test", "cmd", ["cd..\n", "dir\n", "exit\n"])

println "OUT:\n" + out[0]
println "ERR:\n" + out[1]

5
我非常烦恼,因为一个人花时间回答了一个问题,但另一个人没有明显的理由就给它投了反对票。如果这是一个社区,那么应该有义务添加评论(除非是任何有能力的程序员立即能看出来的非常明显的原因)来解释下降的原因。 - Amos Bordowitz
10
很多答案会被踩,这没关系,只是一次踩。但是,这可能是因为没有任何解释的代码并不总是受欢迎。 - Chris Baker
3
@ChrisBaker那为什么不指出来呢?你自己也不能确定这就是原因。 - Amos Bordowitz
8
@AmosBordowitz,我不是官方的“踩贴”解释人员,无法告诉您原因。毕竟我们谈论的是其他人采取的行动,我也不能确定。我提供了一个可能性。为什么不解释一下踩贴的原因呢?当然,也可以解释答案中的代码。无论如何,我相信我们都会没事的。 - Chris Baker
2
@ChrisBaker我从未做出过这样的声明(“但我猜你知道得更好”)。这是一种礼貌的事情,而不是知识上的问题。 - Amos Bordowitz
显示剩余4条评论

-7
command = "ls *"

def execute_state=sh(returnStdout: true, script: command)

但如果命令失败,进程将终止。


1
sh 是从“shell”(壳层)一词中衍生出来的。 - Jerry U
5
“sh” 是 Jenkins Groovy DSL 的一部分。在这里可能没有用处。 - Gi0rgi0s
8
Jenkins的Groovy DSL不等同于Groovy语言。 - Skeeve
正如其他人所说,这是 Jenkins DSL 的一部分。 - jonypony3
1
这个答案与所提出的问题无关。 - Brandon
1
仅适用于Jenkins。 - Fernando Rosado

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接