Uploaded image for project: 'Jenkins'
  1. Jenkins
  2. JENKINS-44930

Allow sh to return exit status, stdout and stderr all at once

      Like many of the commenters on -JENKINS-26133- I'd like to be able to capture the exit status and text written to standard out at the same time.

      My current use case is calling git merge --no-edit $branches and if there was an error sending a slack notification with the output. 

      The current workaround is:

      def status = sh(returnStatus: true, script: "git merge --no-edit $branches > merge_output.txt")
      if (status != 0) {
        currentBuild.result = 'FAILED'
        def output = readFile('merge_output.txt').trim()
        slackSend channel: SLACK_CHANNEL, message: "<${env.JOB_URL}|${env.JOB_NAME}> ran into an error merging the PR branches into the ${TARGET_BRANCH} branch:\n```\n${output}\n```\n<${env.BUILD_URL}/console|See the full output>", color: 'warning', tokenCredentialId: 'slack-token'
        error 'Merge conflict'
      }
      sh 'rm merge_output.txt'

      Which works but isn't a great developer experience... It would be great if I could request an object that contained: status, stdout, and stderr.

          [JENKINS-44930] Allow sh to return exit status, stdout and stderr all at once

          andrew morton created issue -
          andrew morton made changes -
          Link New: This issue is related to JENKINS-26133 [ JENKINS-26133 ]
          andrew morton made changes -
          Description Original: Like many of the commenters on JENKINS-26133 I'd like to be able to capture the exit status and text written to standard out at the same time.

          My current use case is calling \{\{git merge --no-edit $branches}} and if there was an error sending a slack notification with the output. 

          The current workaround is:
          {code:java}
          def status = sh(returnStatus: true, script: "git merge --no-edit $branches > merge_output.txt")
          if (status != 0) {
            currentBuild.result = 'FAILED'
            def output = readFile('merge_output.txt').trim()
          }{code}
          Which works but isn't a great developer experience... It would be great if I could request an object that contained: status, stdout, and stderr.
          New: Like many of the commenters on -JENKINS-26133- I'd like to be able to capture the exit status and text written to standard out at the same time.

          My current use case is calling {{git merge --no-edit $branches}} and if there was an error sending a slack notification with the output. 

          The current workaround is:
          {code:java}
          def status = sh(returnStatus: true, script: "git merge --no-edit $branches > merge_output.txt")
          if (status != 0) {
            currentBuild.result = 'FAILED'
            def output = readFile('merge_output.txt').trim()
            slackSend channel: SLACK_CHANNEL, message: "<${env.JOB_URL}|${env.JOB_NAME}> ran into an error merging the PR branches into the ${TARGET_BRANCH} branch:\n```\n${output}\n```\n<${env.BUILD_URL}/console|See the full output>", color: 'warning', tokenCredentialId: 'slack-token'
            error 'Merge conflict'
          }{code}
          Which works but isn't a great developer experience... It would be great if I could request an object that contained: status, stdout, and stderr.

          Jesse Glick added a comment -

          You can also | tee output.txt, 2> stderr.txt, etc. Given the simple alternatives I am not inclined to make the code more complex for this relatively uncommon use case.

          Jesse Glick added a comment - You can also | tee output.txt , 2> stderr.txt , etc. Given the simple alternatives I am not inclined to make the code more complex for this relatively uncommon use case.

          andrew morton added a comment -

          jglick in my experience piping through tee ends up returning the exit status of tee rather than the git merge. Seems like lots of un-fun work arounds for that: https://stackoverflow.com/questions/985876/tee-and-exit-status

          andrew morton added a comment - jglick in my experience piping through tee  ends up returning the exit status of tee  rather than the git merge . Seems like lots of un-fun work arounds for that:  https://stackoverflow.com/questions/985876/tee-and-exit-status
          andrew morton made changes -
          Description Original: Like many of the commenters on -JENKINS-26133- I'd like to be able to capture the exit status and text written to standard out at the same time.

          My current use case is calling {{git merge --no-edit $branches}} and if there was an error sending a slack notification with the output. 

          The current workaround is:
          {code:java}
          def status = sh(returnStatus: true, script: "git merge --no-edit $branches > merge_output.txt")
          if (status != 0) {
            currentBuild.result = 'FAILED'
            def output = readFile('merge_output.txt').trim()
            slackSend channel: SLACK_CHANNEL, message: "<${env.JOB_URL}|${env.JOB_NAME}> ran into an error merging the PR branches into the ${TARGET_BRANCH} branch:\n```\n${output}\n```\n<${env.BUILD_URL}/console|See the full output>", color: 'warning', tokenCredentialId: 'slack-token'
            error 'Merge conflict'
          }{code}
          Which works but isn't a great developer experience... It would be great if I could request an object that contained: status, stdout, and stderr.
          New: Like many of the commenters on --JENKINS-26133-- I'd like to be able to capture the exit status and text written to standard out at the same time.

          My current use case is calling {{git merge --no-edit $branches}} and if there was an error sending a slack notification with the output. 

          The current workaround is:
          {code:java}
          def status = sh(returnStatus: true, script: "git merge --no-edit $branches > merge_output.txt")
          if (status != 0) {
            currentBuild.result = 'FAILED'
            def output = readFile('merge_output.txt').trim()
            slackSend channel: SLACK_CHANNEL, message: "<${env.JOB_URL}|${env.JOB_NAME}> ran into an error merging the PR branches into the ${TARGET_BRANCH} branch:\n```\n${output}\n```\n<${env.BUILD_URL}/console|See the full output>", color: 'warning', tokenCredentialId: 'slack-token'
            error 'Merge conflict'
          }
          sh 'rm merge_output.txt'{code}
          Which works but isn't a great developer experience... It would be great if I could request an object that contained: status, stdout, and stderr.

          Markus added a comment -

          I don't quite get why this is an uncommon use case? Whenever you do something (anything), there is probably two things you want to know: did it work and if no, what happened. The sh command is currently not able to provide that.

          Redirecting it to a text file would work, but this is far from an elegant solution and opens several other potensial pitfalls. 

          You could catch the return status by using a try/catch around the sh step, but then returnStdout has no effect.

           

          Markus added a comment - I don't quite get why this is an uncommon use case? Whenever you do something (anything), there is probably two things you want to know: did it work and if no, what happened. The sh command is currently not able to provide that. Redirecting it to a text file would work, but this is far from an elegant solution and opens several other potensial pitfalls.  You could catch the return status by using a try/catch around the sh step, but then returnStdout has no effect.  

          Daniel Beck added a comment -

          drewish

          piping through tee ends up returning the exit status of tee rather than the git merge

          Any shell scripts should set errexit, nounset and pipefail anyway, best to get into this habit.

          Daniel Beck added a comment - drewish piping through tee  ends up returning the exit status of tee  rather than the git merge Any shell scripts should set errexit , nounset and pipefail anyway, best to get into this habit.

          andrew morton added a comment - - edited

          danielbeck I agree, that is the best practice for bash scripts. But sh is documented as providing a Bourne shell and pipefail is a bash specific feature. So to use it you'd need to add a shebang to request bash, then manually call set (or create a separate shell script to do so). Again it's possible, but it requires a lot of boilerplate code.

          All I wanted to do was a simple task: run a command and if it fails capture the output to pass to another step. Instead of focusing on that I'm learning all kinds of obscure facts about shell scripting. I thought the whole philosophy of the new pipeline and UI was to make Jenkins more modern and inviting to new users. They shouldn't have to learn all this, the sh could shield them from this the same way that groovy's execute() does.

          andrew morton added a comment - - edited danielbeck I agree, that is the best practice for bash scripts. But sh is documented as providing a Bourne shell and pipefail is a bash specific feature. So to use it you'd need to add a shebang to request bash, then manually call set (or create a separate shell script to do so). Again it's possible, but it requires a lot of boilerplate code. All I wanted to do was a simple task: run a command and if it fails capture the output to pass to another step. Instead of focusing on that I'm learning all kinds of obscure facts about shell scripting. I thought the whole philosophy of the new pipeline and UI was to make Jenkins more modern and inviting to new users. They shouldn't have to learn all this, the sh could shield them from this the same way that groovy's execute() does.

          Jesse Glick added a comment -

          Instead of focusing on that I'm learning all kinds of obscure facts about shell scripting. I thought the whole philosophy of the new pipeline and UI was to make Jenkins more modern and inviting to new users. They shouldn't have to learn all this, the {{sh}}could shield them from this

          No that is the exact opposite of the intent. The Pipeline script is glue code for automating Jenkins operations. Your build processes are your own. If you do not know Bourne shell script and do not care to learn, write Ruby, or whatever.

          Jesse Glick added a comment - Instead of focusing on that I'm learning all kinds of obscure facts about shell scripting. I thought the whole philosophy of the new pipeline and UI was to make Jenkins more modern and inviting to new users. They shouldn't have to learn all this, the {{sh}}could shield them from this No that is the exact opposite of the intent. The Pipeline script is glue code for automating Jenkins operations. Your build processes are your own. If you do not know Bourne shell script and do not care to learn, write Ruby, or whatever.

            Unassigned Unassigned
            drewish andrew morton
            Votes:
            167 Vote for this issue
            Watchers:
            144 Start watching this issue

              Created:
              Updated: