通过Cmd将换行符传递给PowerShell

4

我将尝试从Windows cmd.exe运行PowerShell脚本。输入到PowerShell脚本的是一个字符串,其中包含使用PowerShell反引号转义的换行符 - 例如:

`r`n

为了演示,输入字符串被写入控制台,并且被倾销到文件中。 我的问题是,当脚本使用命令cmd.exe的语法运行时,出现以下问题:
powershell.exe script.ps1 "TEST`r`nTEST"

字符串中的换行字符不会被视为换行符,而是在控制台输出和输出文本文件中字面包含。

TEST`r`nTEST

然而,如果我在PowerShell环境下运行此代码,则会得到预期的结果(即换行符被正确解析,并在适当位置插入换行符)。

TEST
TEST

同样地,如果我通过Windows cmd.exe传入\r\n而不是转义后的换行符,并在PowerShell脚本中执行.replace操作。
$date = $data.replace("\r\n","`r`n")

我得到了预期的输出结果:
TEST
TEST

有人能够解释为什么会出现这种情况吗?
以下是测试脚本:
param([string]$data) # data to send

Write-Host $data
[IO.File]::WriteAllText('d:\temp.txt', $data)
return 0

文件可以通过命令行调用:

powershell.exe script.ps1 "TEST`r`nTEST"

脚本运行在Windows Server 2012 R2上,使用PowerShell v4.0。


顺便提一下:你可能想要使用 exit 0 而不是 return 0 - 后者将会输出一个字面上的 0 而不是设置退出码。 - mklement0
我很困惑为什么你的字符串没有被插值,因为你没有使用“-File”,而默认情况下(直到PSv5.1)是“-Command”。你实际上是否正在使用“-File”,只是忘记在问题中包含它了吗?例如,如果我在v4中运行powershell.exe -NoProfile Write-Output "TEST\r`nTEST"`,我确实会得到_2_行。 - mklement0
@mklement0 有趣的是,powershell.exe script.ps1 "TEST'r'nTEST" 可以正常打印,但 powershell.exe script.ps1 "TEST 'r'nTEST" 只会打印第一个 TEST。在我的真实示例中,我没有使用 -File,但我确实有空格。当您使用 -Write-Output 而不是脚本时,它会打印额外的换行符。 - Sahil Jain
3个回答

4

简而言之

使用 -Command 并将整个 PowerShell 命令作为一个 单独的字符串 传递;例如:

 C:\> powershell -NoProfile -Command "script.ps1 \"TEST`r`nTEST\""
 TEST
 TEST

请注意,内部的 " 实例被转义为 \",这是 PowerShell 从外部调用时所需的(或者,为了完全健壮,可以在 Windows PowerShell 中使用 "^""(sic),在 PowerShell (Core) v6+ 中使用 "")。
在您的特定情况下,powershell -NoProfile -Command script.ps1 "TEST`r`nTEST" 也可以工作,但通常只有在字符串没有嵌入空格的情况下才能按预期工作。鉴于 -Command 是 PSv5.1 的默认值,因此您当前发布的命令应该可以直接使用。

自PowerShell v5.1起,从外部传递给powershell.exe的参数:

  • 默认情况下,PowerShell对字符串插值等内容进行解释,包括使用-Command时(即不指定-File-Command时,默认为-Command)。

  • 如果您使用-File调用脚本,则不会被解释 - (在可能由cmd.exe解释后),PowerShell将所有参数视为字面字符串


可选阅读:为什么在使用-Command时应该将整个PowerShell命令作为一个单一参数传递:

当您使用-Command多个参数时,PowerShell本质上会在执行之前将它们组装成一个单独的命令行。

任何对单个参数的"..."引用都会在此过程中丢失,这可能会导致意外的结果;例如:

C:\> powershell -NoProfile -Command "& { $args.count }" "line 1`r`nline 2"
3   # !! "line 1`r`nline 2" was broken into 3 arguments

鉴于解析命令行时外部的引号"..."被移除,PowerShell 最终执行的实际命令行为:
 C:\ PS> & { $args.Count } line 1`r`nline 2
 3

为了说明这一点,让我们看一个使用显式引用的等效命令:
 C:\ PS> & { $args.Count } "line"   "1`r`nline"   "2"

换句话说:在移除封闭的"之后,结果标记会像往常一样被空格分成多个参数。

1
参数需要重新解释为PowerShell字符串。这会帮助你完成任务吗?
你的-replace没有起作用的原因是原始字符串实际上包含了一个反引号。在搜索字符串中需要对其进行转义。
C:\src\t>type p1.ps1
Param([string]$s)

Write-Host $s

$p = Invoke-Expression `"$s`"
Write-Host $p

$p2 = $s -replace "``r``n","`r`n"
Write-Host $p2


C:\src\t>powershell -noprofile -file .\p1.ps1 "TEST`r`nTEST"
TEST`r`nTEST
TEST
TEST
TEST
TEST

感谢回复。我个人更喜欢传递\r\n,然后在PowerShell中使用'replace'进行替换。我更加好奇的是,为什么会在从cmd调用时发生这种情况。例如,如果你传入\r\n,然后用\r\n替换\r\n,那么\r\n仍然会以字面量形式输出;如果你在PowerShell内部附加\r\n到字符串上,附加的\r\n将作为换行符输出(但现有的仍将以字面量形式输出)。我只是不能理解其背后的逻辑。 - Sahil Jain
关于不转义转义字符串的好发现。我想这确实回答了问题。我只是希望有一个更哲学的原因解释为什么从cmd这样做。 - Sahil Jain
很抱歉我无法更加哲学化地表达。我很高兴你理解了。如果要向一个不了解转义字符是什么的人解释,那将会很困难。 - lit
1
请非常小心这个脚本——这会让传递变量给脚本的人能够执行任意代码。 - Nacht
因此,更喜欢使用替换选项。 - Sahil Jain

1
回车符和换行符是具有值13和10的字节,你无法写出它们,也无法看到它们。
为了方便起见,在编写PowerShell代码时,该语言允许您编写:
"`r`n"

在双引号字符串中,以及处理PowerShell源代码时(仅限此时间),它将读取并将其替换为字节值13和10。
这是PowerShell标记化程序中的此行代码
对于cmd.exe解释器来说,反引号-n没有任何特殊之处,并且在字符串中有它也没有特别之处-您可以在单引号字符串中放置它。
'`n'

除了你必须注意替换发生的时间之外,将其替换为字符串中的内容与替换它没有什么区别。例如,在您的评论中:

例如,如果您传入'r'n,然后用'r'n替换'r'n,则'r'n仍然以字面形式输出

因为您的代码

-replace "`r`n"

变成

-replace "[char]13[char]10"

您从外部传入的字符串包含以下内容:
`r`n

它们不匹配。字符串中的反引号并不是魔法,PowerShell 引擎不会将所有字符串解释为 PowerShell 代码,也不会解释参数或任何其他内容。只有在编写替换代码时,即 实际换行符 的交换发生的上下文中才会发生。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接