GitHub Actions で複数行の文字列を output にセットする方法
GitHub Actions (以下 GHA )で改行文字を含む複数行の文字列をコマンドの output にセットする方法についてです。
追記
2022/11/16
output パラメータをセットする方法が ::setoutput
を使った形から環境変数 GITHUB_OUTPUT
を使った形に変更されたので各所更新しました。
参考:
- GitHub Actions: Deprecating save-state and set-output commands | GitHub Changelog
- Setting an output parameter | Workflow commands for GitHub Actions - GitHub Docs
前提
この記事は当初 2022 年 05 月に書きました。 時間が経つと GitHub Actions の仕様変更などで情報が古くなるので参考にされる際は注意してください。
問題
通常 GitHub Actions では環境変数 GITHUB_OUTPUT
にパスが格納されたファイルに name=value
の形式で文字列を出力すると output をセットできるようになっています。
たとえば、キーが action_fruit
で値が strawberry
のアイテムを output
にセットしたいときは次のように書きます:
run: |
name=action_fruit
value=strawberry
echo "${name}=${value}" >> $GITHUB_OUTPUT
shell: bash
しかし、この方法を使うと、 value
に改行文字が含まれていると改行文字以降が name
の値から抜け落ちてしまうという問題があります。
たとえば、 Hello\nWorld
という文字列を value
にセットすると output パラメータには Hello
だけがセットされてしまいます:
run: |
name=greeting
value="Hello\nWorld"
echo "${name}=${value}" >> $GITHUB_OUTPUT
shell: bash
id: my_command
# => `steps.my_command.outputs.greeting` には `Hello` だけが含まれる
関連イシューなど:
- Set-output Truncates Multiline Strings · Discussion #26288 · community · GitHub
- set-env truncates multiline strings · Issue #403 · actions/toolkit · GitHub
対応方法
GHA で改行文字が含まれうる文字列を output にセットするには次のいずれかの方法を使います
- 方法 A) 事前にエスケープする
- 方法 B) 区切り文字を使う
- 方法 C)
actions/github-script
のcore.setOutput()
を使う
先に結論ですが、おすすめは方法 C) です。 方法 C) なら利用者は implementation detail な改行問題を意識する必要がありません。
方法 A) 事前にエスケープする
標準出力に出力する前に value
内の改行文字などをエスケープします。
たとえば Bash スクリプトの場合は次のような処理を行うことになります:
value="${value//'%'/'%25'}"
value="${value//$'\n'/'%0A'}"
value="${value//$'\r'/'%0D'}"
参考:
- Set-output Truncates Multiline Strings · Discussion #26288 · community · GitHub
- set-env truncates multiline strings · Issue #403 · actions/toolkit · GitHub
方法 B) 区切り文字を使う
GITHUB_OUTPUT
では区切り文字を使うと複数行の文字列をセットできるようになっているのでその記法を使います。
具体的には次のように書きます:
steps:
- name: Set the value in bash
id: step_one
run: |
echo 'JSON_RESPONSE<<EOF' >> $GITHUB_OUTPUT
curl https://example.lab >> $GITHUB_OUTPUT
echo 'EOF' >> $GITHUB_OUTPUT
(これは公式ドキュメントにあるサンプルの $GITHUB_ENV
を $GITHUB_OUTPUT
に変えたものです)
EOF
の部分が区切り文字です。
ここでは JSON_RESPONSE
という output パラメータに curl
コマンドの出力をセットしています。
ただし、このアプローチを使う場合、区切り文字はランダムにするのがセキュリティ上望ましいとされており、実際に使うには注意が必要です:
Warning: Make sure the delimiter you're using is randomly generated and unique for each run. For more information, see "Understanding the risk of script injections".
この点に関して GitHub Community Discussions では次のように区切り文字を動的に生成する方法が紹介されていました:
delimiter="$(openssl rand -hex 8)"
echo "output-name<<${delimiter}" >> "${GITHUB_OUTPUT}"
echo "Some\nMultiline\nOutput" >> "${GITHUB_OUTPUT}"
echo "${delimiter}" >> "${GITHUB_OUTPUT}"
参考:
方法 C) actions/github-script
の core.setOutput()
を使う
GitHub 公式のアクション actions/github-script
の core.setOutput()
を使います。
core.setOutput()
はまさに「改行を含みうる文字列を output にセット」するための関数です。
たとえば次のような形で使用します:
# output `greeting` に 'Hello\nWorld' をセットする
- uses: actions/github-script@v6
with:
script: |
const name = 'greeting'
const value = 'Hello\nWorld'
core.setOutput(name, value)
core.setOutput()
に渡した文字列は内部で方法 A) と同様のエスケープ処理 ↓ が行われてから標準出力に出力されるようになっています。
function escapeData(s: any): string {
return toCommandValue(s)
.replace(/%/g, '%25')
.replace(/\r/g, '%0D')
.replace(/\n/g, '%0A')
}
シンプルでわかりやすいですね。 ということで、方法 A) B) C) の中では最も C) がシンプルでおすすめです。
ちなみに、 actions/github-script
を使う場合は return
でオブジェクトを返せば output をセットできるようにもなっているので、その方法を使うという手もあります。
詳細に興味のある方は actions/github-script
の README を見てみてください:
もうひとつちなみに、 core.setOutput()
を提供している @actions/core
のコードとドキュメントは actions/toolkit
リポジトリの中にあります:
以上です。
おまけ
「コマンドを 1 つ実行してその結果を output にセットする」というパターンは個人的に頻出なので、そのためだけのかんたんなカスタムアクションを作りました: