通过 Cmd 将换行符传递给 PowerShell

Passing newline character to PowerShell via Cmd

我正在尝试 运行 来自 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"

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

参数将需要重新解释为 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

Carriagereturn和Linefeed是字节,值为13和10,不能写,看不到。

为方便起见,在编写 PowerShell 代码时,该语言会让您这样写:

"`r`n"

在双引号字符串中,在处理 PowerShell 源代码时(而不是在其他时间),它会读取这些代码并将它们替换为字节值 13 和 10。

在 PowerShell 分词器中 this line of code 执行此操作。

对于 cmd.exe 解释器来说,backtick-n 没有什么特别之处,将它放在字符串中也没有什么特别之处 - 您可以将它放在单引号字符串中

'`n'

或在字符串中替换它 - 除了您必须注意替换发生的时间。例如在您的评论中:

For example, if you pass in 'r'n and then replace 'r'n with 'r'n, the 'r'n is still output literally

因为你的代码

-replace "`r`n"

变成

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

而你从外部传入的字符串包含

`r`n

而且它们不匹配。字符串中的 Backtick-n 并不神奇,PowerShell 引擎不会将所有字符串都解释为 PowerShell 代码,也不是参数,或任何其他内容。并且只有在这种情况下 - 当您编写 -replace 代码时,即发生 实际换行符 的交换。

tl;dr

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

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

注意内部 " 实例如何转义为 \",当从 外部 调用时,PowerShell 需要这样做(或者,为了完全稳健,使用"^"" (sic) 在 Windows PowerShell""PowerShell (Core) v6+).

在您的具体案例中,
powershell -NoProfile -Command script.ps1 "TEST`r`nTEST" 也可以工作,但通常只有在字符串没有嵌入 spaces.
时才会按预期工作 鉴于 -Command 是 PSv5.1 的 默认 ,您的命令 - 如当前发布的那样 - 应该按原样


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

  • 由 PowerShell 进行解释,包括字符串插值,默认 以及当您使用 -Command(即既不指定 -File 也不指定 -Command 当前默认为 -Command)。

  • 不受解释 如果您使用 -File 调用脚本 -(在cmd.exe 的潜在解释)PowerShell 将所有参数视为 文字字符串 .

    • 警告:此行为是 currently being discussed 相对于 v6,因为它至少在一个方面存在明显问题案例:尝试传递 布尔值 值。

可选阅读:为什么在使用 -Command 时应将 entire PowerShell 命令作为 single 参数传递:

当您将 -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"

换句话说:在删除封闭的 " 之后,生成的标记被 空格 分成 多个 个参数, 和往常一样。