如何在 PowerShell 脚本中使用 shebang?
How can I use a shebang in a PowerShell script?
我有几个 PowerShell 脚本,我想从 Cygwin 中的 Bash shell 作为命令直接调用。例如,如果我用文件名 Write-Foo.ps1 编写脚本,我想从任何工作目录将其作为命令执行:
$ Write-Foo.ps1 arg1 arg2 ...
为此,我将脚本添加到我的 PATH 中,使其可执行,并在文件开头包含以下解释器 shebang/hashbang:
#!/usr/bin/env powershell
Write-Host 'Foo'
...
这是 env 实用程序的常见(滥用)用法,但它将脚本与 Cygwin 的路径前缀(/cygdrive/c/.. .), 至少对于解释器声明。
这可以启动 PowerShell,但系统将文件作为 Cygwin 格式的路径传递,PowerShell 不理解,当然:
The term '/cygdrive/c/path/to/Write-Foo.ps1' is not recognized as the name of a cmdlet, function, script file, or operable program.
MSYS (Git Bash) 似乎正确翻译了脚本路径,并且脚本按预期执行,只要文件路径不包含空格。有没有办法依靠Cygwin中的shebang直接调用PowerShell脚本?
理想情况下,如果可能的话,我还想从脚本名称中省略 .ps1 扩展名,但我知道我可能需要忍受这个局限性。如果可能,我想避免手动为脚本添加别名或包装脚本。
Linux/macOS 用户的快速说明:
- 确保 pwsh 或 powershell 命令在
PATH
- 使用这个解释器指令:
#!/usr/bin/env pwsh
- 确保脚本使用 Unix 风格的行结尾(
\n
,不是 \r\n
)
感谢 briantist 的评论,我现在明白这不是 直接 支持早于 6.0 的 PowerShell 版本而不妥协:
...[in PowerShell Core 6.0] they specifically changed positional parameter 0 from ‑Command
to ‑File
to make that work. ...the error message you're getting is because it's passing a path to ‑Command
...
当我们将脚本作为命令调用时,类 Unix 系统将 PowerShell 脚本的 绝对 文件名作为第一个参数传递给 "shebang" 指定的解释器。一般来说,这 有时 适用于 PowerShell 5 及以下版本,因为默认情况下,PowerShell 将脚本文件名解释为要执行的命令。
但是,我们不能依赖这种行为,因为当 PowerShell 在此上下文中处理 -Command
时,它 重新 解释文件名 as if it was typed at the prompt,因此包含空格或某些符号的脚本路径将破坏 PowerShell 视为参数的 "command"。对于初步解释步骤,我们也损失了一点效率。
当改为指定 -File
参数时,PowerShell 会直接加载脚本,因此我们可以避免使用 -Command
时遇到的问题。不幸的是,要在 shebang 行中使用此选项,我们需要牺牲通过使用问题中描述的 env 实用程序获得的可移植性,因为操作系统程序加载器通常只允许一个参数解释器脚本中声明的程序。
例如,以下解释器指令无效,因为它向 env
命令传递了两个参数(powershell
和 -File
):
#!/usr/bin/env powershell -File
另一方面,在 MSYS 系统(如 Git Bash)中,包含以下指令(具有 PowerShell 的绝对路径)的 PowerShell 脚本按预期执行:
#!/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe -File
...但是我们不能在不遵循相同文件系统约定的另一个系统上直接执行脚本。
这也没有解决 Cygwin 中的原始问题。如问题中所述,脚本本身 的路径未转换为 Windows 样式的路径,因此 PowerShell 无法找到该文件(即使在版本 6 中)。我想出了几个解决方法,但都没有提供完美的解决方案。
最简单的方法就是利用 PowerShell 的 -Command
参数的默认行为。将 Write-Foo.ps1
脚本添加到环境的命令搜索路径 (PATH
) 后,我们可以使用脚本名称调用 PowerShell,没有扩展名:
$ powershell Write-Foo arg1 arg2 ...
只要脚本文件本身的文件名中不包含空格,这就允许我们从任何工作目录 运行 脚本——不需要 shebang。 PowerShell 使用本机例程来解析来自 PATH
的命令,因此我们无需担心父目录中的空格。但是,我们丢失了 Bash 的命令名称的制表符补全。
为了让 shebang 在 Cygwin 中工作,我需要编写一个代理脚本,将调用脚本的路径样式转换为 PowerShell 可以理解的格式。我将其命名为 pwsh(为了与 PS 6 的可移植性)并将其放置在 PATH
:
#!/bin/sh
if [ ! -f "" ]; then
exec "$(command -v pwsh.exe || command -v powershell.exe)" "$@"
exit $?
fi
script="$(cygpath -w "")"
shift
if command -v pwsh.exe > /dev/null; then
exec pwsh.exe "$script" "$@"
else
exec powershell.exe -File "$script" "$@"
fi
脚本首先检查第一个参数。如果它不是文件,我们就正常启动 PowerShell。否则,脚本会将文件名转换为 Windows 样式的路径。如果版本 6 中的 pwsh.exe 不可用,则此示例回退到 powershell.exe。然后我们可以在脚本中使用下面的解释器指令...
#!/usr/bin/env pwsh
...并直接调用脚本:
$ Write-Foo.ps1 arg1 arg2 ...
对于 6.0 之前的 PowerShell 版本,如果我们想创建没有扩展名的原件。
我有几个 PowerShell 脚本,我想从 Cygwin 中的 Bash shell 作为命令直接调用。例如,如果我用文件名 Write-Foo.ps1 编写脚本,我想从任何工作目录将其作为命令执行:
$ Write-Foo.ps1 arg1 arg2 ...
为此,我将脚本添加到我的 PATH 中,使其可执行,并在文件开头包含以下解释器 shebang/hashbang:
#!/usr/bin/env powershell
Write-Host 'Foo'
...
这是 env 实用程序的常见(滥用)用法,但它将脚本与 Cygwin 的路径前缀(/cygdrive/c/.. .), 至少对于解释器声明。
这可以启动 PowerShell,但系统将文件作为 Cygwin 格式的路径传递,PowerShell 不理解,当然:
The term '/cygdrive/c/path/to/Write-Foo.ps1' is not recognized as the name of a cmdlet, function, script file, or operable program.
MSYS (Git Bash) 似乎正确翻译了脚本路径,并且脚本按预期执行,只要文件路径不包含空格。有没有办法依靠Cygwin中的shebang直接调用PowerShell脚本?
理想情况下,如果可能的话,我还想从脚本名称中省略 .ps1 扩展名,但我知道我可能需要忍受这个局限性。如果可能,我想避免手动为脚本添加别名或包装脚本。
Linux/macOS 用户的快速说明:
- 确保 pwsh 或 powershell 命令在
PATH
- 使用这个解释器指令:
#!/usr/bin/env pwsh
- 确保脚本使用 Unix 风格的行结尾(
\n
,不是\r\n
)
感谢 briantist 的评论,我现在明白这不是 直接 支持早于 6.0 的 PowerShell 版本而不妥协:
...[in PowerShell Core 6.0] they specifically changed positional parameter 0 from
‑Command
to‑File
to make that work. ...the error message you're getting is because it's passing a path to‑Command
...
当我们将脚本作为命令调用时,类 Unix 系统将 PowerShell 脚本的 绝对 文件名作为第一个参数传递给 "shebang" 指定的解释器。一般来说,这 有时 适用于 PowerShell 5 及以下版本,因为默认情况下,PowerShell 将脚本文件名解释为要执行的命令。
但是,我们不能依赖这种行为,因为当 PowerShell 在此上下文中处理 -Command
时,它 重新 解释文件名 as if it was typed at the prompt,因此包含空格或某些符号的脚本路径将破坏 PowerShell 视为参数的 "command"。对于初步解释步骤,我们也损失了一点效率。
当改为指定 -File
参数时,PowerShell 会直接加载脚本,因此我们可以避免使用 -Command
时遇到的问题。不幸的是,要在 shebang 行中使用此选项,我们需要牺牲通过使用问题中描述的 env 实用程序获得的可移植性,因为操作系统程序加载器通常只允许一个参数解释器脚本中声明的程序。
例如,以下解释器指令无效,因为它向 env
命令传递了两个参数(powershell
和 -File
):
#!/usr/bin/env powershell -File
另一方面,在 MSYS 系统(如 Git Bash)中,包含以下指令(具有 PowerShell 的绝对路径)的 PowerShell 脚本按预期执行:
#!/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe -File
...但是我们不能在不遵循相同文件系统约定的另一个系统上直接执行脚本。
这也没有解决 Cygwin 中的原始问题。如问题中所述,脚本本身 的路径未转换为 Windows 样式的路径,因此 PowerShell 无法找到该文件(即使在版本 6 中)。我想出了几个解决方法,但都没有提供完美的解决方案。
最简单的方法就是利用 PowerShell 的 -Command
参数的默认行为。将 Write-Foo.ps1
脚本添加到环境的命令搜索路径 (PATH
) 后,我们可以使用脚本名称调用 PowerShell,没有扩展名:
$ powershell Write-Foo arg1 arg2 ...
只要脚本文件本身的文件名中不包含空格,这就允许我们从任何工作目录 运行 脚本——不需要 shebang。 PowerShell 使用本机例程来解析来自 PATH
的命令,因此我们无需担心父目录中的空格。但是,我们丢失了 Bash 的命令名称的制表符补全。
为了让 shebang 在 Cygwin 中工作,我需要编写一个代理脚本,将调用脚本的路径样式转换为 PowerShell 可以理解的格式。我将其命名为 pwsh(为了与 PS 6 的可移植性)并将其放置在 PATH
:
#!/bin/sh
if [ ! -f "" ]; then
exec "$(command -v pwsh.exe || command -v powershell.exe)" "$@"
exit $?
fi
script="$(cygpath -w "")"
shift
if command -v pwsh.exe > /dev/null; then
exec pwsh.exe "$script" "$@"
else
exec powershell.exe -File "$script" "$@"
fi
脚本首先检查第一个参数。如果它不是文件,我们就正常启动 PowerShell。否则,脚本会将文件名转换为 Windows 样式的路径。如果版本 6 中的 pwsh.exe 不可用,则此示例回退到 powershell.exe。然后我们可以在脚本中使用下面的解释器指令...
#!/usr/bin/env pwsh
...并直接调用脚本:
$ Write-Foo.ps1 arg1 arg2 ...
对于 6.0 之前的 PowerShell 版本,如果我们想创建没有扩展名的原件。