任何程序都可以作为 GitHub Actions 的 Shell

2025-04-07

在 GitHub Actions 中,你可以使用 shell 关键字来指定运行 run: 代码块的 shell。 这个关键字对于工作流是可选的,但对于 action 定义是强制的。

通常,shell 默认为你的 runner 提供一些合理的默认值,例如 Linux 和 macOS 上的 bash 以及 Windows 上的 pwsh。 但它也可以被显式指定,并且 GitHub 文档说明,显式指定它也意味着会使用一些 GitHub 选择的标志:

# 显式设置 bash 意味着 bash --noprofile --norc -eo pipefail
- shell: bash
 run: |
  echo "Hello, world!"

基于此,你可能会认为 shell 值有一个固定的有效数量1,GitHub 跟踪并添加其特殊标志。 但你错了!

事实证明,你可以将 shell 设置为 $PATH 上的任何可执行文件,GitHub 会很乐意使用它来执行 run 代码块。 如果该命令尚未将单个文件作为输入,则需要将特殊的 {0} 参数传递给它,GitHub 会将该参数替换为临时文件,该文件将模板扩展的 run 代码块生成到该临时文件中。

多亏了这一点,我们可以做各种疯狂的事情。

使用 C 作为我们的步骤 runner 效果很好:

- run: sudo apt install -y tcc
- shell: tcc -run {0}
 run: |
  #include <stdio.h>
  int main() {
   printf("Hello, world!\n");
   return 0;
  }

...动态修改 $PATH 通过 $GITHUB_PATH 也是可行的: dynamically modifying:

- run: |
  touch ./bash
  chmod +x ./bash
  echo '#!/bin/sh' > ./bash
  echo 'echo hello from fake bash' >> ./bash
  echo "${PWD}" >> "${GITHUB_PATH}"
- run: |
  echo "this doesn't do what you expect"
 shell: bash

从安全角度来看,这重要吗? 很难说 - 在 GitHub Actions 中,文件写入意味着执行有很多其他方式,包括 GITHUB_ENV 本身。

另一方面,令人惊讶的是 (IMO) GitHub 甚至对其“众所周知”的 shell 值执行 $PATH 查找; 我希望这些值被固定为特定值(例如,bash/bin/bash),特别是由于它们基于这些众所周知的值将标志注入到 run 的命令行中。

  1. 或者,更一般地说,GitHub 将接受其 toolcache 中的任何预注册工具。 例如,这就是我之前认为 shell: python 的工作方式,直到我注意到这一点。