Multiline strings as a job output in GitHub Actions


Context

GitHub Actions allows to use the outputs from dependent jobs. For single line strings, you can do the following

jobs:
  job1:
    runs-on: ubuntu-latest
    outputs:
      output1: ${{ steps.set-output.outputs.output1 }}
    steps:
      - id: set-output
        run: |
                    echo "::set-output name=output1::'Hello World'"
  job2:
    runs-on: ubuntu-latest
    needs: [job1]
    steps:
      - run: |
                    echo "Output1: ${{ needs.job1.outputs.output1 }}"

Unfortunately, the above solution doesn’t work for multiline strings. By default, using the :::set-output command will cut the string to just the first line of content.

Implementation

We have 2 main options at this time when creating new multiline outputs to be used by downstream jobs:

  • Escape the return characters to convert the multiline string into a single-line one. The runner will convert back appropriately when the output is used.
  • Leverage the actions/github-script and the core context (from the actions/toolkit package) to export the output through javascript

Escaping the characters through Bash string substitution

Let’s see how escaping would work in detail. The characters that need escaping are %, \n and \r, based on this github community question.

Leveraging string substitution in bash, this looks as follows:

content="${content//'%'/'%25'}"
content="${content//$'\n'/'%0A'}"
content="${content//$'\r'/'%0D'}"

Then, the jobs would look like:

jobs:
  job1:
    runs-on: ubuntu-latest
    outputs:
      output1: ${{ steps.set-output.outputs.output1 }}
    steps:
      - id: set-output
        run: |
          # Assuming we have a $content variable already populated 
          content="${content//'%'/'%25'}"
          content="${content//$'\n'/'%0A'}"
          content="${content//$'\r'/'%0D'}"
          echo "::set-output name=output1::$content"          
  job2:
    runs-on: ubuntu-latest
    needs: [job1]
    steps:
      - run: |
                    echo "Output1: ${{ needs.job1.outputs.output1 }}"

Using action/github-script and the core package in actions/toolkit

The github-script action provides access to different APIs accessible through different objects. The core object referencees actions/core package, which allows setting the step’s output using the setOutput function/

jobs:
  job1:
    runs-on: ubuntu-latest
    outputs:
      output1: ${{ steps.set-output.outputs.output1 }}
    steps:
      - uses: actions/github-script@v5
        id: set-output
        with:
          script: |
            # Assuming we have a `content` environment variable or output of previous step
            core.setOutput("output1", `${{ env.context }}`);            
  job2:
    runs-on: ubuntu-latest
    needs: [job1]
    steps:
      - run: |
                    echo "Output1: ${{ needs.job1.outputs.output1 }}"

References

To learn more about it, check the following resources:

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer’s view in any way.