How do I get the output of a shell command executed using into a variable from Jenkinsfile (groovy)? – Dev

The best answers to the question “How do I get the output of a shell command executed using into a variable from Jenkinsfile (groovy)?” in the category Dev.

QUESTION:

I have something like this on a Jenkinsfile (Groovy) and I want to record the stdout and the exit code in a variable in order to use the information later.

sh "ls -l"

How can I do this, especially as it seems that you cannot really run any kind of groovy code inside the Jenkinsfile?

ANSWER:

Current Pipeline version natively supports returnStdout and returnStatus, which make it possible to get output or status from sh/bat steps.

An example:

def ret = sh(script: 'uname', returnStdout: true)
println ret

An official documentation.

ANSWER:

The latest version of the pipeline sh step allows you to do the following;

// Git committer email
GIT_COMMIT_EMAIL = sh (
    script: 'git --no-pager show -s --format=\'%ae\'',
    returnStdout: true
).trim()
echo "Git committer email: ${GIT_COMMIT_EMAIL}"

Another feature is the returnStatus option.

// Test commit message for flags
BUILD_FULL = sh (
    script: "git log -1 --pretty=%B | grep '\\[jenkins-full]'",
    returnStatus: true
) == 0
echo "Build full flag: ${BUILD_FULL}"

These options where added based on this issue.

See official documentation for the sh command.

For declarative pipelines (see comments), you need to wrap code into script step:

script {
   GIT_COMMIT_EMAIL = sh (
        script: 'git --no-pager show -s --format=\'%ae\'',
        returnStdout: true
    ).trim()
    echo "Git committer email: ${GIT_COMMIT_EMAIL}"
}

ANSWER:

If you want to get the stdout AND know whether the command succeeded or not, just use returnStdout and wrap it in an exception handler:

scripted pipeline

try {
    // Fails with non-zero exit if dir1 does not exist
    def dir1 = sh(script:'ls -la dir1', returnStdout:true).trim()
} catch (Exception ex) {
    println("Unable to read dir1: ${ex}")
}

output:

[Pipeline] sh
[Test-Pipeline] Running shell script
+ ls -la dir1
ls: cannot access dir1: No such file or directory
[Pipeline] echo
unable to read dir1: hudson.AbortException: script returned exit code 2

Unfortunately hudson.AbortException is missing any useful method to obtain that exit status, so if the actual value is required you’d need to parse it out of the message (ugh!)

Contrary to the Javadoc https://javadoc.jenkins-ci.org/hudson/AbortException.html the build is not failed when this exception is caught. It fails when it’s not caught!

Update:
If you also want the STDERR output from the shell command, Jenkins unfortunately fails to properly support that common use-case. A 2017 ticket JENKINS-44930 is stuck in a state of opinionated ping-pong whilst making no progress towards a solution – please consider adding your upvote to it.

As to a solution now, there could be a couple of possible approaches:

a) Redirect STDERR to STDOUT 2>&1
– but it’s then up to you to parse that out of the main output though, and you won’t get the output if the command failed – because you’re in the exception handler.

b) redirect STDERR to a temporary file (the name of which you prepare earlier) 2>filename (but remember to clean up the file afterwards) – ie. main code becomes:

def stderrfile="stderr.out"
try {
    def dir1 = sh(script:"ls -la dir1 2>${stderrfile}", returnStdout:true).trim()
} catch (Exception ex) {
    def errmsg = readFile(stderrfile)
    println("Unable to read dir1: ${ex} - ${errmsg}")
}

c) Go the other way, set returnStatus=true instead, dispense with the exception handler and always capture output to a file, ie:

def outfile="stdout.out"
def status = sh(script:"ls -la dir1 >${outfile} 2>&1", returnStatus:true)
def output = readFile(outfile).trim()
if (status == 0) {
    // output is directory listing from stdout
} else {
    // output is error message from stderr
}

Caveat: the above code is Unix/Linux-specific – Windows requires completely different shell commands.

ANSWER:

quick answer is this:

sh "ls -l > commandResult"
result = readFile('commandResult').trim()

I think there exist a feature request to be able to get the result of sh step, but as far as I know, currently there is no other option.

EDIT: JENKINS-26133

EDIT2: Not quite sure since what version, but sh/bat steps now can return the std output, simply:

def output = sh returnStdout: true, script: 'ls -l'