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?
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}"
}
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.
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'
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.
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]} "
}
}
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.
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.
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
}
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
}
}
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 ( ` ).
I am trying to update with this script my extension app which is inside the main app.
In general when i commit with svn, the version of my main app update, now i need to update the extension version also.
I am trying to use the following script but seems it gives error. any idea?
this is the example:
version_number=$1
build_number=$2
#
echo "version_number is $version_number"
echo "build_number is $build_number"
pruvitInfoPlist="ServiceExtension/Info.plist"
/usr/libexec/PlistBuddy -c "Set CFBundleShortVersionString $version_number" $pruvitInfoPlis
The Error:
> Build/file.rb:41: syntax error, unexpected unary-, expecting do or '{'
> or '(' /usr/libexec/PlistBuddy -c "Set CFBundleShortVersionSt...
> ^ Build/file.rb:41: syntax error, unexpected tGVAR, expecting end-of-input ...ersion_number" $pruvitInfoPlist ...
> ^~~~~~~~~~~~~~~~ Command PhaseScriptExecution failed with a nonzero
> exit code
I am going to Answer my question here maybe in can help someone else.
I resolved the issue with script in extension.
plistFile = "#{ENV['BUILT_PRODUCTS_DIR']}/#{ENV['INFOPLIST_PATH']}"
`/usr/bin/plutil -convert xml1 "#{plistFile}"`
unless pl = Plist::parse_xml(plistFile)
puts "Could parse #{plistFile}"
exit
end
freshPlFile = "#{ENV['SOURCE_ROOT']}/ServiceExtension/Info.plist"
`/usr/bin/plutil -convert xml1 "#{freshPlFile}"`
unless freshPl = Plist::parse_xml(freshPlFile)
puts "Could parse #{freshPlFile}"
exit
end
version = pl["CFBundleShortVersionString"].gsub(/ \([a-f0-9 \/:]*\)/, '')
# keep only the major and minor version number and add the revision
version.gsub!(/([^\.]*)\.([^\.]*).*/, "\\1.\\2.#{revision}");
pl["CFBundleShortVersionString"] = version
pl["CFBundleVersion"] = Time.now.utc.strftime("%Y%m%d%H")
pl.save_plist(plistFile)
`/usr/bin/plutil -convert binary1 #{plistFile}`
puts "#{plistFile}:"
puts "CFBundleVersion = #{pl["CFBundleVersion"]}"
puts "CFBundleShortVersionString = #{pl["CFBundleShortVersionString"]}"
every time i commit it update my extension and my main app. you need to add this script also in main app.
I have a Jenkins scripted pipeline set up where I execute a number of Maven builds. I want to treat one of them as non-fatal if the root cause is a known one.
I have tried to achieve that by inspecting the Exception's message, e.g.
try {
sh "mvn -U clean verify sonar:sonar ${sonarcloudParams}"
} catch ( Exception e ) {
if ( e.getMessage().contains("not authorized to run analysis")) {
echo "Marking build unstable due to missing SonarCloud onboarding. See https://cwiki.apache.org/confluence/display/SLING/SonarCloud+analysis for steps to fix."
currentBuild.result = 'UNSTABLE'
}
}
The problem is that the exception's message is not the one from Maven, but instead "script returned exit code 1".
There is no further information in e.getCause().
How can I access the cause of the Maven build failure inside my scripted pipeline?
You can get the command output, then parse it containers specific message.
def output = sh(
script: "mvn -U clean verify sonar:sonar ${sonarcloudParams}",
returnStdout: true
).trim()
echo "mvn cmd output: ${output}"
if(output.contains('not authorized to run analysis')) {
currentBuild.result = 'UNSTABLE'
}
// parse jenkins job build log
def logUrl = env.BUILD_URL + 'consoleText'
def cmd = "curl -u \${JENKINS_AUTH} -k ${logUrl} | tail -n 50"
def output = sh(returnStdout: true, script: cmd).trim()
echo "job build log: ${output}"
if(output.contains('not authorized to run analysis')) {
currentBuild.result = 'UNSTABLE'
}
One option is to inspect the last log lines using
def sonarCloudNotEnabled = currentBuild.rawBuild.getLog(50).find {
line -> line.contains("not authorized to run analysis")
}
However, this does not work by default. On the Jenkins instance I'm using it errors out with
Scripts not permitted to use method org.jenkinsci.plugins.workflow.support.steps.build.RunWrapper getRawBuild. Administrators can decide whether to approve or reject this signature.
How can I capture the output of a command and check for what it says without the command displaying in stdout? For example:
def update!
`git pull origin master`
if $?.exitstatus > 0
puts 'Failed to update'
elsif $?.success?
puts 'Upgraded successfully'
else
puts 'Already up to date'
end
end
How can I capture the output of this in order to check whether the command says up-to date, an error occurs, or successfully updates? Is there a way to write the output to a file instead of to the console?
Update for answer:
def update!
update_status = `git pull origin master 2>&1`
if $?.exitstatus > 0
puts 'error'
elsif update_status =~ /Already up-to date/
puts 'same version as origin master'
else
puts 'updated'
end
end
The output for this will always be:
[06:44:29 INFO] Updating to newest version..
updated
Even if the version is the same as the origin. What I would like to do, if possible, is save the stdout of the command to a file and read from that file to discover if the program was updated or not. I think that would be the easiest way to go about doing this.
You can assign the output of the command to a string.
Use 2>&1 to redirect stderr to stdout and thus capture all the output.
str = `git pull origin master 2>&1`
if $?.exitstatus > 0
...
elsif str =~ /up-to-date/
...
else
...
end
desc "Takes a database dump from remote server"
task :backup do
invoke 'db:backup_name'
on roles(:db) do
run "mysqldump -u #{fetch(:db_username)} -p#{fetch(:db_password)} #{fetch(:db_database)} > #{shared_path}/db_backups/#{fetch(:backup_filename)}.sql"
system('mkdir -p db_backups')
download! "#{fetch(:backup_file)}", "db_backups/#{fetch(:backup_filename)}.sql"
within release_path do
execute :rm, "#{fetch(:backup_file)}"
end
end
end
I get this error:
wrong number of arguments (1 for 0)
on this line:
run "mysqldump -u #{fetch(:db_username)} -p#{fetch(:db_password)} #{fetch(:db_database)} > #{shared_path}/db_backups/#{fetch(:backup_filename)}.sql"
If I puts the contents of this and run this on the remote server - it works fine. Really not sure why ruby throws this error.
Try using backticks instead:
`mysqldump -u #{fetch(:db_username)} -p#{fetch(:db_password)} #{fetch(:db_database)} > #{shared_path}/db_backups/#{fetch(:backup_filename)}.sql`