如何在Jenkinsfile(Groovy)中执行一个shell命令并将输出存储到一个变量中?

364

我在 Jenkinsfile (Groovy) 中有类似这样的内容,我想记录标准输出和退出码以便稍后使用该信息存储在一个变量中。

sh "ls -l"

如何实现这一点,特别是因为似乎无法在 Jenkinsfile 内部运行任何类型的 Groovy 代码?


3
可否在Jenkins流水线中捕获sh DSL命令的标准输出(stdout)? - Jesse Glick
可能是[在管道中捕获sh DSL命令的标准输出是否可行]的重复问题(https://dev59.com/EFoV5IYBdhLWcg3wPcxv)。 - mkobit
10个回答

625

管道的最新版本sh步骤使您可以执行以下操作;

// 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}"

另一个特性是 returnStatus 选项。

// 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}"

这些选项是基于问题添加的。

有关sh命令,请查看官方文档

对于声明性管道(请参见注释),您需要将代码包装到script步骤中:

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

13
看起来现在已经有文档记录了 -> https://jenkins.io/doc/pipeline/steps/workflow-durable-task-step/#code-sh-code-shell-script - zot24
@G.Roggemans 如何在 Docker 容器中运行它?我尝试在 dockerimg.inside {} 中运行相同的内容,但没有成功。 - Prem Sompura
15
当我使用声明式 Jenkinsfile 语法时,出现错误,错误信息为:WorkflowScript: 97: Expected a step @ line 97, column 17. - Wrench
29
似乎这只在 script 步骤块内起作用。参见 https://jenkins.io/doc/book/pipeline/syntax/#declarative-steps。 - brass monkey
4
在官方文档链接https://jenkins.io/doc/pipeline/steps/workflow-durable-task-step/#sh-shell-script中,我没有看到任何关于sh步骤或其选项如returnStdout的参考。这仍然是正确的文档链接吗? 答案:在官方文档链接 https://jenkins.io/doc/pipeline/steps/workflow-durable-task-step/#sh-shell-script 中,我未看到任何有关 sh 步骤或其选项(例如 returnStdout)的参考。这是否为正确的文档链接? - Phalgun
显示剩余10条评论

93

当前的Pipeline版本原生支持returnStdoutreturnStatus,这使得可以从sh/bat步骤获取输出或状态。

例如:

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

官方文档


有人能帮我解决这个问题吗?链接:https://dev59.com/5p3ha4cB1Zd3GeqPOQsr 谢谢! - Jitesh Sojitra
10
这些声明需要用 script { } 包裹。 - x-yuri
页面链接的文档已经没有关于sh的任何信息了... :( - Anentropic

52

快速答案如下:

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

我认为存在一项功能请求,允许获取sh步骤的结果,但据我所知,目前没有其他选项。

修改:JENKINS-26133

修改2:不确定是从哪个版本开始,但现在sh/bat步骤可以简单地返回标准输出:

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

2
另外顺便提一下,批处理步骤会回显正在运行的命令,因此您需要在批处理命令前加上@以仅获取输出(例如“@dir”)。 - Russell Gallop
我使用了以下代码替换@符号: output = sh(script: 'command here', returnStdout: true).trim().readLines().drop(1).join(" ") - itodd

44

如果您想要获取标准输出并且知道该命令是否成功,只需使用returnStdout并将其包装在异常处理程序中:

脚本化管道

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}")
}

输出:

[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

不幸的是,hudson.AbortException缺少获取退出状态的有用方法,因此如果需要实际值,您需要从消息中解析它(唉!)

与Javadoc相反https://javadoc.jenkins-ci.org/hudson/AbortException.html,当捕获到此异常时,构建失败。 它是在未被捕获时失败的!

更新:如果您还想要来自shell命令的STDERR输出,则不幸的是,Jenkins无法适当地支持常见的用例。 2017票JENKINS-44930卡在了一个主观的乒乓球状态中,没有向解决方案取得进展-请考虑将您的赞投票给它。

至于现在的解决方案,可能会有几种可能的方法:

a)将STDERR重定向到STDOUT2>&1-但是您需要从主输出中解析它,并且如果命令失败,则不会获得输出-因为您在异常处理程序中。

b)将STDERR重定向到临时文件(名称提前准备)2>filename(但记得在之后清理文件)-即主代码变为:

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)相反地,设置returnStatus=true,摒弃异常处理程序并始终将输出捕获到文件中,即:

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
}

注意:上面的代码适用于 Unix/Linux 系统,而Windows需要完全不同的命令。


1
有没有可能得到输出为“ls:无法访问dir1:没有那个文件或目录”,而不仅仅是“hudson.AbortException:脚本返回退出代码2”? - user2988257
2
我不认为这能够工作。在我的测试中,输出文本从未被指定,这是可以预见的。来自外壳步骤的异常会阻止返回值被分配。 - Jakub Bochenski
2
很遗憾,returnStatus和returnStdout不能同时工作。这是票证:https://issues.jenkins-ci.org/browse/JENKINS-44930。请投票。 - Alexander Samoylov
1
@AlexanderSamoylov 你必须使用文件作为上述选项(c)的解决方法。不幸的是,这些工具的作者通常很有自己的想法,并且没有考虑其他常见的用例,这里所说的'sh'就是一个例子。 - Ed Randall
1
@Ed Randall,我完全同意你的观点。这就是为什么我发布了这个问题,希望由于更多的投票,他们开始采取行动。 - Alexander Samoylov
@AlexanderSamoylov 转到 GitLab 吧 ;) - Ed Randall

12

这是一个示例案例,我相信它将有意义!

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


    echo "${commit[-1]} "

    }
}

12

对于那些需要在后续 shell 命令中使用输出结果的人,可以使用类似以下示例的方法:

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

我发现code maven上的例子非常有用。


2
所有上述方法都可以使用,但是要在代码内将变量作为环境变量使用,您需要首先导出该变量。
script{
  sh " 'shell command here' > command"
  command_var = readFile('command').trim()
  sh "export command_var=$command_var"
}

用您选择的命令替换shell命令。如果您正在使用Python代码,现在可以仅指定os.getenv("command_var"),它将返回先前执行的shell命令的输出。


1

如果您不只有一个sh命令而是一组sh命令,那么returnstdout就无法工作。

我曾经遇到过类似的问题,我采用了一种不太干净的方法来解决它,但最终它起到了作用并达到了目的。

解决方案 - 在shell块中,echo该值并将其添加到某个文件中。 在脚本块内部和外部的shell块之间,读取此文件,修剪它并将其分配给任何本地/参数/环境变量。

例如 -

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


     }
}


0
如何在Groovy中读取shell变量/如何将shell返回值赋给Groovy变量。
要求:打开一个文本文件,使用shell读取行,并将值存储在Groovy中,并获取每行的参数。
这里是分隔符。
例如: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

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

在这里想要获取模块名称第二个参数(configurable-wf-report),构建编号为第三个参数(94),提交标识符为第四个参数(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

                        

}

-6

最简单的方法是使用以下方式:

my_var=`echo 2` echo $my_var 输出结果为:2

请注意,这里不是使用单引号而是反引号(`)。


1
已点赞,但我建议您表明这些应该在“sh”下包装,否则人们可能会认为它是Groovy,特别是如果他们不熟悉Bash脚本。我刚刚在Jenkins上尝试了一下,使用ls -l而不是echo 2,它可以工作。实际上,我以前就用过这种方法,但一直在寻找替代方案,因为它不太可靠。我以这种方式在标准shell中捕获了更复杂命令的输出,但当移植到Jenkins的“sh”时,变量无法保留,原因未知。 - Nagev

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