为什么 shebang 行中带引号字符串的 env -S 在 Ubuntu 中工作正常,但在 macOS 中却不行?

为什么 shebang 行中带引号字符串的 env -S 在 Ubuntu 中工作正常,但在 macOS 中却不行?

我的可执行文件中有以下脚本test-shebang.mjs,我想用它zx来运行我的脚本,但~/.zshrc在此之前要先获取我的脚本:

#!/usr/bin/env -S zsh -c 'source ~/.zshrc; zx --install $@' --

console.log("work pls")

./test-shebang.mjs在 Ubuntu 中工作正常:

❯ cat /etc/os-release
PRETTY_NAME="Ubuntu 22.04.4 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.4 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy

❯ zsh --version
zsh 5.8.1 (x86_64-ubuntu-linux-gnu)

但是当我在 macOS 中复制相同的脚本时,出现以下错误:

❯ ./test-shebang.mjs
zsh:1: unmatched '

❯ zsh --version
zsh 5.9 (x86_64-apple-darwin23.0)

❯ sw_vers
ProductName:        macOS
ProductVersion:     14.4
BuildVersion:       23E214

为什么会出现这种情况?

我也尝试过,bash但也遇到了错误。 FWIW,我正在做这种迂回的做事方式,因为我已经zx通过pnpm它安装了它本身brew,我不想设置PATH它只会使脚本更长。

答案1

Shebang 行在不同 Unix 内核上的解析略有不同。在 Linux 和现代 BSD 上,该命令后跟一个参数(或无参数),该参数可以包含空格。在 macOS 上,该命令后跟零个或多个用空格分隔的参数。

因此,在 Linux 上,当您使用./test-shebang.mjs参数运行时foo,会发生以下情况:

  • 内核/usr/bin/env使用参数运行-S zsh -c 'source ~/.zshrc; zx --install $@' --(请注意,所有这些都是第一个参数), ./test-shebang.mjs, foo
  • envzsh使用参数-c, source ~/.zshrc; zx --install $@, --, ./test-shebang.mjs,运行foo
  • source ~/.zshrc; zx --install $@Zsh使用脚本名称--和两个位置参数./test-shebang.mjs,运行脚本foo
  • 返回后.zshrc,zshzx使用参数--install, ./test-shebang.mjs,运行foo

在 macOS 上,第一步是不同的(继承自旧版本的 FreeBSD),这会导致在第三步中检测到意外的数据:

  • 内核/usr/bin/env使用参数-S, zsh, -c, 'source, ~/.zshrc;, zx, --install, $@', --, ./test-shebang.mjs,运行foo
  • envzsh使用参数-c, 'source, ~/.zshrc;, zx, --install, $@', --, ./test-shebang.mjs,运行foo
  • 'sourceZsh使用脚本名称~/.zshrc;和位置参数zx, --install, $@', --, ./test-shebang.mjs,运行脚本foo
  • Zsh 抱怨这'source不是一个语法正确的脚本。

不幸的是,env -S在 Linux 和 FreeBSD 上弥补了它们的 shebang 解析怪异,但 macOS 将 FreeBSD 的env实用程序与不同的 shebang 解析混合在一起,因此env -S在 macOS 上无法按预期工作(或以任何特别有用的方式)。

复杂 shebangs 的便携式解决方案是编写一个多语言带有#!/bin/shshebang,第二行包含专门编写的 shell 代码,无论脚本使用什么语言,都可以执行无操作。例如,这里有一个 sh+JavaScript 多语言(假设一个 JS 变体将 shebang 行视为注释)和你的 shebang 行做同样的事情。

#!/bin/sh
eval : '; exec zsh -c "source ~/.zshrc; zx --install \$@" -- "$0" "$@"';
console.log("Javascript code starts here");
  • eval在 sh 中,第二行是带有两个参数:和 的内置命令; exec zsh -c "source ~/.zshrc; zx --install \$@" -- "$0" "$@"。这会导致 sh 运行: ; exec zsh -c "source ~/.zshrc; zx --install \$@" -- "$0" "$@":是一个无操作命令。内置exec导致 sh 将自身替换为 zsh,因此 sh 在第二行之后停止解析文件。
  • 在 JavaScript 中,第二行是一个字符串文字(没有效果),带有标签eval(无用但无害)。

请注意,您在脚本中放入的内容确实很奇怪并且不太可能有帮助。.zshrc用于交互式定制,在非交互式 shell 中使用时可能会导致奇怪的效果。.zshrc不应设置环境变量,因为这些变量在非终端程序中不可用,并且如果运行嵌套的 zsh,这些变量将被重置;你应该设置环境变量.zprofile

相关内容