232

Groovy adds the execute method to String to make executing shells fairly easy;

println "ls".execute().text

but if an error happens, then there is no resulting output. Is there an easy way to get both the standard error and standard output? (other than creating a bunch of code to create two threads to read both inputstreams, using a parent stream to wait for them to complete, and then convert the strings back to text?)

It would be nice to have something like;

 def x = shellDo("ls /tmp/NoFile")
 println "out: ${x.out} err:${x.err}"
1
  • This link is useful. Shows how to run shell command with cURL demo. Commented Aug 16, 2014 at 6:02

8 Answers 8

303

Ok, solved it myself;

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

displays:

out> err> ls: cannot access /badDir: No such file or directory

Sign up to request clarification or add additional context in comments.

5 Comments

In case you also need to set Environment Variables to this process, make sure to wrap the command in shell. For example, running a Perforce command with env vars: 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);
@paul_sns unrelated to the OP question, but I think modern JVMs handle uncontended synchronization just fine. So StringBuffer is unlikely to degrade performance in thread- or stack-confined scenarios .
The docs say that we should be using waitForProcessOutput() - "To wait for the output to be fully consumed call waitForProcessOutput()". Source: docs.groovy-lang.org/latest/html/groovy-jdk/java/lang/…
@srikanth the waitForProcess() output docs also say "Use this method if you don't care about the standard or error output and just want the process to run silently" - I want the output
sout and serr might not be available even after the waitForOrKill. Tested using an assert instead of a println. Docs say: "For this, two Threads are started, so this method will return immediately. The threads will not be join()ed, even if waitFor() is called. To wait for the output to be fully consumed call waitForProcessOutput()."
67

"ls".execute() returns a Process object which is why "ls".execute().text works. You should be able to just read the error stream to determine if there were any errors.

There is a extra method on Process that allow you to pass a StringBuffer to retrieve the text: consumeProcessErrorStream(StringBuffer error).

Example:

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

println proc.text
println b.toString()

2 Comments

It is not working with Bourn Again Shell script !#/bin/bash,
If working with bash scripts, you you probably invoke bash as part of the command : "/bin/bash script".execute()
41

I find this more idiomatic:

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"

As another post mentions, these are blocking calls, but since we want to work with the output, this may be necessary.

Comments

39
// 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()
}

4 Comments

+1 This shows the output incrementally as the output is generated..which is extremely important for a long running process
To use this solution, issue the following line: runCommand("echo HELLO WORLD")
@mholm815 how can we approve required scripts from pipeline itself?
@RNK: He left approx. 2017: "Last seen more than 6 years ago"
29

To add one more important piece of information to the previous answers:

For a process

def proc = command.execute();

always try to use

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

rather than

def output = proc.in.text;

to capture the outputs after executing commands in Groovy as the latter is a blocking call (SO question for reason).

Comments

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]

9 Comments

I am really annoyed that a person took the time to give an answer and someone just downvoted it for no apparent reason. if this is a community, one should feel obligated to add a comment (unless it's a very obvious reason that any competent programmer would immediately see) explaining the downvote.
@AmosBordowitz Lots of answers get downvotes. It's okay, it's one downvote. That said, it could be because it's code with no word of explanation -- not always well-received.
@ChrisBaker so why not point it out? You yourself are not positive that this is the reason..
@AmosBordowitz I am not the official downvote explainer, I cannot tell you why not, and it's understandable that I'm not certain since we're talking about an action taken by another individual. I offered one possibility. Why not explain the downvote, sure, why not explain the code in the answer? At any rate, I'm sure we'll all be okay.
@ChrisBakerI never made any such claim ("but I guess you know better"). It's a decency thing, not a knowledge thing..
|
0

Complementing emles-kz's answer

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 + '\n').getBytes(encoding))
        inputCatcher.flush()
    }
  
    inputCatcher.write('exit\n'.getBytes(encoding))
    inputCatcher.flush()
    proc.consumeProcessOutput(outputCatcher, errorCatcher)
    proc.waitFor()
    return [new String(outputCatcher.toByteArray(), encoding), new String(errorCatcher.toByteArray(), encoding)]
}

// On Mac or Linux
def out = exec("utf-8", "/", "/bin/sh", ["echo 'test\n'", "ls", "cd usr", "echo", "ls"])

// On Windows
def out = exec("utf-8", "C:", "cmd", ["cd..", "dir"])

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

jenkins groovy test

Comments

-7
command = "ls *"

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

but if the command fails, the process will terminate.

6 Comments

Where does sh come from?
sh is part of the Jenkins groovy DSL. Probably not useful here
Jenkins Groovy DSL != Groovy
This answer is not applicable to the question that was asked.
Only valid for jenkins
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.