如何在PowerShell脚本中使用shebang?

Cy *_*nol 8 bash shell powershell cygwin shebang

我有几个PowerShell脚本,我想直接从Cygwin中的Bash shell调用命令.例如,如果我编写一个文件名为Write-Foo.ps1的脚本,我想将其作为来自任何工作目录的命令执行:

$ Write-Foo.ps1 arg1 arg2 ...
Run Code Online (Sandbox Code Playgroud)

为此,我将脚本添加到PATH中,使其可执行,并在文件开头包含以下解释器shebang/hashbang:

#!/usr/bin/env powershell

Write-Host 'Foo'
...
Run Code Online (Sandbox Code Playgroud)

这是env实用程序的常见(ab)用法,但它将脚本与Cygwin的路径前缀(/ cygdrive/c/...)分离,至少对于解释器声明是这样.

这可以启动PowerShell,但系统将文件作为Cygwin格式的路径传递,PowerShell当然不理解,当然:

术语"/cygdrive/c/path/to/Write-Foo.ps1"未被识别为cmdlet,函数,脚本文件或可操作程序的名称.

MSYS(Git Bash)似乎正确地转换了脚本路径,并且脚本按预期执行,只要该文件的路径不包含空格即可.有没有办法通过依赖Cygwin中的shebang直接调用PowerShell脚本?

理想情况下,如果可能的话,我还想从脚本名称中省略.ps1扩展名,但我知道我可能需要忍受这个限制.我想尽可能避免手动别名或包装脚本.


Linux/macOS用户发现这一点的快速说明.

Cy *_*nol 13

Linux/macOS用户快速注意到这一点:

  • 确保pwshpowershell命令在PATH
  • 使用此解释器指令: #!/usr/bin/env pwsh
  • 确保脚本使用Unix风格的行结束符(\n,不是 \r\n)

感谢briantist的评论,我现在明白,这不是直接支持早于6.0的PowerShell版本而不妥协:

... [ 在PowerShell核心6.0 ]他们专门从改变位置参数0 ‑Command,以‑File使这项工作....你得到的错误信息是因为它传递了一条路径‑Command......

当我们将脚本作为命令调用时,类Unix系统将PowerShell脚本的绝对文件名传递给由"shebang"指定的解释器作为第一个参数.通常,这有时适用于PowerShell 5及更低版本,因为PowerShell默认将脚本文件名解释为要执行的命令.

但是,我们不能依赖于这种行为,因为当PowerShell -Command在此上下文中处理 时,它会重新解释文件名,就像它在提示符下键入一样,因此包含空格或某些符号的脚本的路径将破坏"命令" PowerShell将其视为参数.我们在初步解释步骤中也失去了一些效率.

在指定-File参数时,PowerShell会直接加载脚本,因此我们可以避免遇到的问题-Command.不幸的是,要在shebang行中使用这个选项,我们需要通过使用问题中描述的env实用程序来牺牲我们获得的可移植性,因为操作系统程序加载器通常只允许在脚本中为解释器声明的程序有一个参数.

例如,以下解释器指令无效,因为它将两个参数传递给env命令(powershell-File):

#!/usr/bin/env powershell -File
Run Code Online (Sandbox Code Playgroud)

另一方面,在MSYS系统(如Git Bash)中,包含以下指令(具有PowerShell的绝对路径)的PowerShell脚本按预期执行:

#!/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe -File
Run Code Online (Sandbox Code Playgroud)

...但我们不能直接在不遵循相同文件系统约定的另一个系统上执行该脚本.


这也无法解决Cygwin中的原始问题.如问题中所述,脚本本身的路径未转换为Windows样式的路径,因此PowerShell无法找到该文件(即使在版本6中).我找到了几个解决方法,但都没有提供完美的解决方案.

最简单的方法就是利用PowerShell -Command参数的默认行为.将Write-Foo.ps1脚本添加到环境的命令搜索路径(PATH)后,我们可以使用脚本名称调用PowerShell,而不是扩展名:

$ powershell Write-Foo arg1 arg2 ...
Run Code Online (Sandbox Code Playgroud)

只要脚本文件本身在文件名中不包含空格,这就允许我们从任何工作目录运行脚本 - 不需要shebang.PowerShell使用本机例程来解析命令PATH,因此我们不需要担心父目录中的空格.但是,我们失去了命令名称的Bash标签完成功能.

为了让shebang在Cygwin中工作,我需要编写一个代理脚本,将调用脚本的路径样式转换为PowerShell可以理解的格式.我称它为pwsh(为了便携性与PS 6)并将其放在PATH:

#!/bin/sh

if [ ! -f "$1" ]; then 
    exec "$(command -v pwsh.exe || command -v powershell.exe)" "$@"
    exit $?
fi

script="$(cygpath -w "$1")"
shift

if command -v pwsh.exe > /dev/null; then 
    exec pwsh.exe "$script" "$@"
else
    exec powershell.exe -File "$script" "$@"
fi
Run Code Online (Sandbox Code Playgroud)

该脚本首先检查第一个参数.如果它不是文件,我们只是正常启动PowerShell.否则,脚本会将文件名转换为Windows样式的路径.如果版本6中的pwsh.exe不可用,则此示例将回退到powershell.exe.然后我们可以在脚本中使用以下解释器指令......

#!/usr/bin/env pwsh
Run Code Online (Sandbox Code Playgroud)

...并直接调用脚本:

$ Write-Foo.ps1 arg1 arg2 ...
Run Code Online (Sandbox Code Playgroud)

对于6.0之前的PowerShell版本,如果我们要创建没有扩展名的原始文件,则可以将脚本扩展为符号链接或写出扩展名为.ps1的临时PowerShell脚本.