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.mjsUbuntuではうまくいきます:

❯ 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、私はすでに自分zxpnpmインストールしていて、スクリプトを長くするために設定したくbrewないので、このバイパス作業方法を実行しています。PATH

答え1

Shebang の行は、他の Unix カーネルでわずかに異なって解析されます。。 Linuxおよび最新のBSDでは、コマンドの後にスペースを含めることができる引数(または引数なし)が続きます。 macOSでは、コマンドの後に空白で区切られた0個以上の引数が続きます。

したがって、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はスクリプト名--と2つの位置パラメータを使用して./test-shebang.mjsスクリプトを実行しますfoo
  • 戻った後、.zshrczshはパラメータ、、zxで実行されます。--install./test-shebang.mjsfoo

macOSでは最初のステップが異なるため(以前のバージョンのFreeBSDから継承されている)、3番目のステップでは予期しないデータが検出されます。

  • カーネルは、、、、、、、、、、、パラメータ/usr/bin/envで実行されます。-Szsh-c'source~/.zshrc;zx--install$@'--./test-shebang.mjsfoo
  • envzsh-c、、、、、、、、、パラメータを使用して'source実行します。~/.zshrc;zx--install$@'--./test-shebang.mjsfoo
  • 'sourceZshはスクリプト名と~/.zshrc;場所パラメータzx、、、、、を使用してスクリプトを実行します。--install$@'--./test-shebang.mjsfoo
  • Zshは、これが'source構文上正しいスクリプトではないと文句を言います。

残念ながら、env -SLinuxとFreeBSDはShebanの構文解析の奇妙な点を補いますが、macOSはenvFreeBSDのユーティリティを他のShebanの構文解析と混在させるので、env -SmacOSでは期待どおり(または特に便利な方法で)機能しません。

複雑なshebangのための移植可能な解決策は、以下を書くことです。多言語shebangの#!/bin/sh2行目には、スクリプトで使用されている言語に関係なく何もしない特別に書かれたシェルコードが含まれています。たとえば、次はshebang行と同じことを行うsh + JavaScriptマルチ言語(shebang行をコメントとして扱うJSバリアントを想定)です。

#!/bin/sh
eval : '; exec zsh -c "source ~/.zshrc; zx --install \$@" -- "$0" "$@"';
console.log("Javascript code starts here");
  • evalshの2行目は:2つのパラメータと; exec zsh -c "source ~/.zshrc; zx --install \$@" -- "$0" "$@"。これによりshが実行されます: ; exec zsh -c "source ~/.zshrc; zx --install \$@" -- "$0" "$@":動作しないコマンドです。組み込み機能のexecため、sh はそれ自体 zsh に置き換えられるため、sh は 2 行目以降のファイルの解析を停止します。
  • JavaScriptでは、2行目は文字列リテラル(効果なし)であり、ラベルeval(役に立たないが無害)があります。

スクリプトに入れた内容は本当に奇妙で役に立つとは思えません。.zshrc対話型のカスタマイズに使用され、非対話型シェルで使用すると、奇妙な効果が発生する可能性があります。.zshrc環境変数は非ターミナルプログラムでは使用できず、ネストされたzshを実行するとリセットされるため、環境変数を設定しないでください.zprofile

関連情報