如何使 ($Line_1`n$Line_2) 在 CMD 脚本中工作?

How to make ($Line_1`n$Line_2) work in a CMD script?

我在CMD脚本文件中使用了以下代码

PowerShell Add-Type -AssemblyName System.Windows.Forms;^
$Line_1 = 'Hello!';^
$Line_2 = 'How are you?';^
[System.Windows.Forms.MessageBox]::Show($Line_1)

以上只会显示($Line_1)
如果使用 ($Line_1`n$Line_2),则不会显示任何内容。

如何让它同时显示 $Line_1$Line_2?

您可以查找多种连接字符串的方法,但最简单的方法可能是使用 + 符号。 `n 是一个换行符,它将第 2 行放在第 1 行下方。请注意,` 是一个反引号(通常与波浪号位于同一键上 ~)

[System.Windows.Forms.MessageBox]::Show($Line_1 + "`n" + $Line_2)

再次查看您的 post 后,我发现您与 ($Line_1`n$Line_2) 很接近。你只是缺少一些双引号

[System.Windows.Forms.MessageBox]::Show("$Line_1`n$Line_2")

Powershell 乐于用双引号内的值替换变量。您可以阅读有关可扩展字符串的更多信息 and here

显然$Line_1 + "`n" + $Line_2"$Line_1`n$Line_2"正常工作。将命令字符串从 cmd 发送到具有其遗留怪癖的 PowerShell 只是很棘手,因为:

  • 在cmd中()是各个地方的特殊字符,表示一个块
  • 令牌分隔符不仅是<space><tab>,还有; , = <0x0B> <0x0C> and <0xFF>。这会更改 cmd 的标记化行为,但被调用的命令可能会再次使用其规则重新解析命令

根据文档,PowerShell 期望在最后一个参数 中使用 单个字符串 中的命令(这并不完全正确,因为文档未正确更新),因此您需要引用整个内容 或转义所有分隔符。最简单的解决方案是使用单行并像这样

转义 "`n" 中的引号
PowerShell "Add-Type -AssemblyName System.Windows.Forms; $Line_1 = 'Hello!'; $Line_2 = 'How are you?'; [System.Windows.Forms.MessageBox]::Show($Line_1 + \"`n\" + $Line_2)"

如果你想将命令放在多行中,那么你不能引用字符串。现在要将整个内容作为一个参数,您需要转义所有空格(在这种情况下,您不需要以某种方式转义 ;,可能是因为在将命令行传递给 PowerShell 后,它会调用 GetCommandLineW 并再次解析整个事情本身)

PowerShell Add-Type^ -AssemblyName^ System.Windows.Forms;^
$Line_1^ =^ 'Hello!';^
$Line_2^ =^ 'How^ are^ you?';^
[Windows.Forms.MessageBox]::Show($Line_1^ +^ \"`n\"^ +^ $Line_2)"

或者,您可以通过直接使用 [char]10

获取新行来避免 "`n" 字符串
PowerShell -Command Add-Type -AssemblyName System.Windows.Forms;^
$Line_1 = 'Hello!';^
$Line_2 = 'How are you?';^
[System.Windows.Forms.MessageBox]::Show($Line_1 + [char]10 + $Line_2)

最后一个无需任何转义即可工作的解决方案利用 PowerShell 的 EncodedCommand 选项接收 UTF-16 命令字符串的 base64 编码字符串。您可以通过 运行 在 PowerShell

中获取编码版本
$str = @'
>> Add-Type -AssemblyName System.Windows.Forms;
>> $Line_1 = 'Hello!';
>> $Line_2 = 'How are you?';
>> [System.Windows.Forms.MessageBox]::Show($Line_1 + "`n" + $Line_2)
>> '@
PS C:\Users\phucl> [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($str))
QQBkAGQALQBUAHkAcABlACAALQBBAHMAcwBlAG0AYgBsAHkATgBhAG0AZQAgAFMAeQBzAHQAZQBtAC4AVwBpAG4AZABvAHcAcwAuAEYAbwByAG0AcwA7AAoAIAA9ACAAJwBIAGUAbABsAG8AIQAnADsACgAgAD0AIAAnAEgAbwB3ACAAYQByAGUAIAB5AG8AdQA/ACcAOwAKAFsAUwB5AHMAdABlAG0ALgBXAGkAbgBkAG8AdwBzAC4ARgBvAHIAbQBzAC4ATQBlAHMAcwBhAGcAZQBCAG8AeABdADoAOgBTAGgAbwB3ACgAIAArACAAIgAKACIAIAArACAAKQA=

获得编码版本后,您可以从 cmd 调用它

PowerShell -EncodedCommand QQBkAGQALQBUAHkAcABlACAALQBBAHMAcwBlAG0AYgBsAHkATgBhAG0AZQAgAFMAeQBzAHQAZQBtAC4AVwBpAG4AZABvAHcAcwAuAEYAbwByAG0AcwA7AAoAJABMAGkAbgBlAF8AMQAgAD0AIAAnAEgAZQBsAGwAbwAhACcAOwAKACQATABpAG4AZQBfADIAIAA9ACAAJwBIAG8AdwAgAGEAcgBlACAAeQBvAHUAPwAnADsACgBbAFMAeQBzAHQAZQBtAC4AVwBpAG4AZABvAHcAcwAuAEYAbwByAG0AcwAuAE0AZQBzAHMAYQBnAGUAQgBvAHgAXQA6ADoAUwBoAG8AdwAoACQATABpAG4AZQBfADEAIAArACAAIgBgAG4AIgAgACsAIAAkAEwAaQBuAGUAXwAyACkA

最简单的解决方案是(注意 \"$Line_1`n$Line_2\" 部分):

PowerShell -c Add-Type -AssemblyName System.Windows.Forms; ^
$Line_1 = 'Hello!'; ^
$Line_2 = 'How are you?'; ^
[System.Windows.Forms.MessageBox]::Show(\"$Line_1`n$Line_2\")

请注意,我明确添加了 -c (-Command) 参数名称以表示正在传递 PowerShell 命令字符串。虽然这在默认为 -CommandWindows PowerShell 中不是必需的,但在 PowerShell (Core) 7+[=247 中是必需的=],其中 -File 现在是默认值 - 请参阅 CLI 文档 了解 Windows PowerShell and PowerShell (Core) 7+

也就是说,你必须"..."中使用$Line_1`n$Line_2,一个expandable string,你必须\-转义 " 个字符 以便 PowerShell 不会将它们作为命令行解析的一部分(在没有 overall double 的情况下-引用,""" 也有效)。


不幸的是,当使用 for /f 时,解析规则 发生变化 以便逐行处理 PowerShell 的输出 and/or 将其捕获到变量中:

注意:以下使用 [Console]::WriteLine() 来产生控制台输出,只是为了使用与 [System.Windows.Forms.MessageBox]::Show() 方法调用类似的语法,同时允许捕获某些内容通过 for /f。在现实生活中,没有很好的理由调用[Console]::WriteLine()

for /f "delims=" %%l in ('

  PowerShell -c Add-Type -AssemblyName System.Windows.Forms^; ^
  $Line_1 ^= 'Hello!'^; ^
  $Line_2 ^= 'How are you?'^; ^
  [Console]::WriteLine^(\"$Line_1`n$Line_2\"^)

') do echo [%%l]
  • = , ; ( ) 必须 另外 被转义(在 cmd.exe 视为 "..." 字符串的范围之外)。

  • 如果您在 "..." 中另外包含一个 \"...\" 字符串以防止白色 space 规范化(请参阅下一节),您必须 ^-escaped封闭(外部)";例如,
    ^"\"Marley & Me\"^"

  • 续行(^ 在命令内部行的末尾)实际上是 for /f 中的 可选 ,但它们包括在内以保持一致性。


引号转义要求总结:

  • 你的带有续行的多行技术(^ 在行尾)——语法上不能使用"..."引用 entire 命令,因为 cmd.exe 不支持双引号多行 strings - 需要小心 ^-转义应传递给 PowerShell 的所有 cmd.exe 元字符,特别是 & | < > ^,此外, 如果从以下位置调用 PowerShell在 for /f 语句 , ; = ( ) 中 - 除非这些字符恰好是 cmd.exe 视为 double 的子字符串的一部分-引;例如,& 放在 \"...\" 字符串内 - 例如\"$Line_1`n & $Line_2\" - 必须 而不是 ^ 转义(但请参阅下面的重新白色 space 规范化)。

    • 作为例外,元字符 % 必须始终转义为 %%(仅适用于 批处理文件 ,不适用于命令提示符 - 请参阅 this answer).

    • 此外,如果setlocal enabledelayedexpansion生效(或者cmd.exe/V:ON开始),!也必须转义,但莫名其妙如下:

      • As ^^! (sic) 之外 cmd.exe 视为 "..." 字符串(其他元字符只需要 一个^)
      • ^!里面这样的字符串(其他元字符需要转义)
    • 当通过 for /f 调用时,续行是 可选的 - 也就是说,您可以省略末尾的 ^命令内部线。

  • 每个语句必须以 ; 结尾(正如您所做的),因为行继续符 (^) 导致 no 输入行之间的换行符,因此 PowerShell 将它们视为单行,多个语句必须 ; 分隔。

  • 因为 cmd.exe 和 PowerShell 的 initial 命令行解析都不知道单引号字符串 ('...') 并且因为escaped \"...\" 字符串中的 " 字符在命令行解析时没有 syntactic 功能,这样的字符串被分解成 多个参数如果它们包含白色space:

    • 实际上,多个相邻spaces在这些字符串中的运行被标准化为单个 space每个。例如,'How are you?'\"How are you?\" 最终被 PowerShell 视为 'How are you?'"How are you?"

    • 为了避免这种情况,另外 将这些字符串包含在 "..." 中:

      • -引号 PowerShell 字符串:"'How are you?'"
        • 注意:如果整个命令包含在"..."
        • 中,则不需要
      • 双引号 PowerShell 字符串:^"\"How are you?\"^"
        • ^-转义包含 " 个字符。确保 cmd.exe 仍然将内部 \"...\" 之间的内容视为双引号(因为它不将 \ 识别为转义字符。),避免需要 ^-在那里转义 cmd.exe 个元字符。
        • 注意:如果整个命令包含在"..."中,则需要不同的方法:使用"... "^""How are you?"^"" ..."powershell.exe(Windows PowerShell),以及 "... ""How are you?"" ..."pwsh.exe (PowerShell (Core) 7+)。
  • 如果你想在 PowerShell 代码中包含 comments,你必须使用表单
    ^<# ... #^> - 即(转义)内联 注释 - 支持正常的单行注释 (# ....)(因为它需要 newline 来终止它们,但手头的调用方法中的语句之间没有换行符。


PowerShell 如何解析传递给其 CLI 的 -Command / -c 参数的参数:

PowerShell 让您可以选择传递命令字符串

  • 或者:作为单个参数,包含在整体"..."中;例如:

    • powershell -c "Get-Date -Format 'yyyy MMM'"
  • 或:多个 个参数 - 可能单独 "..." - 引用 - 然后 PowerShell 将其连接起来形成一个字符串;例如:

    • powershell -c Get-Date -Format "'yyyy MMM'"

在这两种情况下,未转义 " 个字符从参数中 剥离,因为它们被假定为为了 命令行 而不是生成的 PowerShell 命令,仅具有句法功能。

在剥离语法 " 字符并将结果参数与 spaces 连接后,如果适用,Powershell 将结果字符串 解释为 PowerShell 代码

注意:这与您使用 -File CLI 参数调用 脚本文件 并将参数传递给它时解析参数的方式有根本不同 - 请参阅 更多。