357

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?

Sergey Pleshakov
  • 7,964
  • 2
  • 17
  • 40
sorin
  • 161,544
  • 178
  • 535
  • 806
  • 3
    Duplicate of http://stackoverflow.com/questions/36507410/is-it-possible-to-capture-the-stdout-from-the-sh-dsl-command-in-the-pipeline. – Jesse Glick Apr 11 '16 at 17:17
  • Possible duplicate of [Is it possible to capture the stdout from the sh DSL command in the pipeline](https://stackoverflow.com/questions/36507410/is-it-possible-to-capture-the-stdout-from-the-sh-dsl-command-in-the-pipeline) – mkobit Nov 15 '18 at 23:04

10 Answers10

615

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}"
}
scrutari
  • 1,378
  • 2
  • 17
  • 33
G. Roggemans
  • 6,456
  • 1
  • 11
  • 8
  • 13
    Looks like now it's documented -> https://jenkins.io/doc/pipeline/steps/workflow-durable-task-step/#code-sh-code-shell-script – zot24 Sep 02 '16 at 11:03
  • Doesn't work for me with the "vars" prefix though. When I just use GIT_COMMIT_EMAIL as var name without the prefix all is fine. – Bastian Voigt Dec 02 '16 at 13:00
  • Please help me for http://stackoverflow.com/questions/40946697/cant-store-sh-command-output-through-dsl-groovy-in-jenkins-pipeline-job – Jitesh Sojitra Dec 03 '16 at 10:37
  • @BastianVoigt, the `vars` is a custom class I use for global variables. This might be confusing. edit: I've removed the class. – G. Roggemans Dec 06 '16 at 13:07
  • The first cmd doesn't seem to work for me when running a git ls-remote but I was able to use the 2nd ok with the git cmd and grep to get a boolean – Nimjox Jun 26 '17 at 21:23
  • Don't forget about `trim()` at the end of `sh` call. Otherwise you may end-up with output with new-line character at the end. – luka5z Jul 05 '17 at 06:37
  • This is fine but how do we get the traceback? – Arvind Jan 02 '18 at 11:16
  • @G.Roggemans How to run it inside docker container? I tried running same in dockerimg.inside {}, did not work. – Prem Sompura Jan 25 '18 at 18:08
  • 15
    When I'm using declarative jenkinsfile syntax, this doesn't work, error message is: ```WorkflowScript: 97: Expected a step @ line 97, column 17.``` – Wrench Mar 20 '18 at 15:17
  • 29
    It seems that this works only inside a `script` step block. https://jenkins.io/doc/book/pipeline/syntax/#declarative-steps – brass monkey Apr 10 '18 at 14:42
  • Note that this returns the command itself as well as the stdout of the command. For example, in this answer's code, `GIT_COMMIT_EMAIL` will contain something like `myPrompt$ git --no-pager show -s --format=\'%ae\' \n email@address.xyz` – cowlinator Oct 09 '19 at 23:40
  • 4
    In the official documentation link https://jenkins.io/doc/pipeline/steps/workflow-durable-task-step/#sh-shell-script, I do not see any reference to sh step or its options like returnStdout. It is still the correct link for documetation? – Phalgun Jan 13 '21 at 11:49
  • Now it only has node and ws steps but, back in November 2020, in the most recent snapshot in the Wayback Machine, https://web.archive.org/web/20201130165731/https://www.jenkins.io/doc/pipeline/steps/workflow-durable-task-step/, it had bat, node, powershell, pwsh, sh and ws; sh being the one we want here. – Martin Dorey Jan 22 '21 at 00:41
  • for grepping a string I get always error even with the returnStdout: true, like `TRIGGER_THE_PIPELINE = sh( script: 'git log -1 |tail -1 |grep JK#', returnStdout: true )` – tuxErrante Apr 22 '21 at 11:28
  • what is `==0` for? – divine Apr 12 '23 at 12:57
91

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.

luka5z
  • 7,525
  • 6
  • 29
  • 52
52

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'
vehovmar
  • 1,557
  • 1
  • 14
  • 24
  • 2
    Also FYI, bat steps echo the command being run so you need to start bat commands with @ to just get the output (e.g. "@dir"). – Russell Gallop Jul 08 '19 at 15:54
  • Instead of `@` I used `output = sh(script: 'command here', returnStdout: true).trim().readLines().drop(1).join(" ")` – itodd Jul 19 '21 at 02:28
44

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.

Ed Randall
  • 6,887
  • 2
  • 50
  • 45
  • 1
    is there a chance to get the output as "ls: cannot access dir1: No such file or directory" and not just "hudson.AbortException: script returned exit code 2" ? – user2988257 Nov 13 '18 at 09:36
  • 2
    I don't see how this could ever work. In my testing the output text is never assigned and this is to be expected. Exception thrown from the shell step prevents the return value to be assigned – Jakub Bochenski Jun 26 '19 at 11:05
  • 2
    returnStatus and returnStdout do not work at the same time unfortunately. Here is the ticket. Please, vote: https://issues.jenkins-ci.org/browse/JENKINS-44930. – Alexander Samoylov Oct 10 '19 at 16:14
  • 1
    @AlexanderSamoylov You have to work-around using a file as in option (c) above. Unfortunately the authors of these tools are often opinionated and don't think ahead for other common use-cases, 'sh' here being a case in point. – Ed Randall Oct 11 '19 at 06:08
  • 1
    @Ed Randall, Fully agree with you.. This is why I posted this issue hoping that due to bigger number of votes they start doing something. – Alexander Samoylov Oct 11 '19 at 09:27
  • @AlexanderSamoylov move to GitLab ;) – Ed Randall Oct 11 '19 at 13:14
13

this is a sample case, which will make sense I believe!

node('master'){
    stage('stage1'){
    def commit = sh (returnStdout: true, script: '''echo hi
    echo bye | grep -o "e"
    date
    echo lol''').split()


    echo "${commit[-1]} "

    }
}
Lorenz Meyer
  • 19,166
  • 22
  • 75
  • 121
Bibek Mantree
  • 346
  • 3
  • 9
11

For those who need to use the output in subsequent shell commands, rather than groovy, something like this example could be done:

    stage('Show Files') {
        environment {
          MY_FILES = sh(script: 'cd mydir && ls -l', returnStdout: true)
        }
        steps {
          sh '''
            echo "$MY_FILES"
          '''
        }
    }

I found the examples on code maven to be quite useful.

Nagev
  • 10,835
  • 4
  • 58
  • 69
2

All the above method will work. but to use the var as env variable inside your code you need to export the var first.

script{
  sh " 'shell command here' > command"
  command_var = readFile('command').trim()
  sh "export command_var=$command_var"
}

replace the shell command with the command of your choice. Now if you are using python code you can just specify os.getenv("command_var") that will return the output of the shell command executed previously.

1

If you don't have a single sh command but a block of sh commands, returnstdout wont work then.

I had a similar issue where I applied something which is not a clean way of doing this but eventually it worked and served the purpose.

Solution - In the shell block , echo the value and add it into some file. Outside the shell block and inside the script block , read this file ,trim it and assign it to any local/params/environment variable.

example -

steps {
     script {
            sh '''
               echo $PATH>path.txt
               // I am using '>' because I want to create a new file every time to get the newest value of PATH
            '''
            path = readFile(file: 'path.txt')
            path = path.trim() //local groovy variable assignment

            //One  can assign these values to env and params as below  - 
            env.PATH = path  //if you want to assign it to env var
            params.PATH  = path //if you want to assign it to params var


     }
}

Dharman
  • 30,962
  • 25
  • 85
  • 135
0

How to read the shell variable in groovy / how to assign shell return value to groovy variable.

Requirement : Open a text file read the lines using shell and store the value in groovy and get the parameter for each line .

Here , is delimiter

Ex: releaseModule.txt

./APP_TSBASE/app/team/i-home/deployments/ip-cc.war/cs_workflowReport.jar,configurable-wf-report,94,23crb1,artifact



./APP_TSBASE/app/team/i-home/deployments/ip.war/cs_workflowReport.jar,configurable-temppweb-report,394,rvu3crb1,artifact

========================

Here want to get module name 2nd Parameter (configurable-wf-report) , build no 3rd Parameter (94), commit id 4th (23crb1)

def  module = sh(script: """awk -F',' '{ print \$2 "," \$3 "," \$4 }' releaseModules.txt  | sort -u """, returnStdout: true).trim()

echo module

List lines = module.split( '\n' ).findAll { !it.startsWith( ',' ) }

def buildid

def Modname

lines.each {

List det1 =  it.split(',')

buildid=det1[1].trim() 

Modname = det1[0].trim()

tag= det1[2].trim()

               

echo Modname               

echo buildid

                echo tag

                        

}
kk.
  • 3,747
  • 12
  • 36
  • 67
VRPL
  • 1
-6

Easiest way is use this way

my_var=`echo 2` echo $my_var output : 2

note that is not simple single quote is back quote ( ` ).

Ajay Gadhavana
  • 415
  • 5
  • 5
  • 1
    Upvoted, but I'd suggest that you show that these should be wrapped under a `sh` otherwise people might think it's groovy, especially if they're not familiar with bash scripting. I've just tried it on Jenkins, using `ls -l` instead of `echo 2` and it works. I had actually used this approach before, but been searching for an alternative because, it's not very reliable. I have the output of a more complex command captured on a standard shell in this way, but when ported to the Jenkins `sh` the variable holds nothing, for some unknown reason. – Nagev Nov 13 '19 at 15:32