tl;dr:
至少 PowerShell 7.2.x,如果您需要 原始字节处理 和/或需要防止 PowerShell 在情境中添加 尾随换行符文本数据,完全避免使用 PowerShell 管道。
对于原始字节处理,使用/c 输出到cmd(在Windows 上;在类Unix 平台/类Unix Windows 子系统上,使用sh 或@ 987654333@ 和-c):
cmd /c 'type .\test.txt | .\Crypt.exe --encrypt | .\Crypt.exe --decrypt'
使用类似的技术将原始字节输出保存在 文件 - 不要使用 PowerShell >运营商:
cmd /c 'someexe > file.bin'
请注意,如果您想在 PowerShell 变量中捕获外部程序的 文本 输出或在 PowerShell 管道中进一步处理它,您需要确保 @987654338 @ 匹配程序的输出字符编码(通常是活动的 OEM 代码页),在这种情况下默认情况下应该为 true;有关详细信息,请参阅下一节。
一般来说,最好避免字节操作文本数据。
有两个独立的问题,其中只有一个有简单的解决方案:
问题一:确实存在字符编码问题,正如你所怀疑的:
PowerShell 隐形 将自己作为中介插入管道中,即使在向外部程序发送数据和从其接收数据时也是如此:它将数据从 .NET 转换为 .NET字符串 (System.String),它们是UTF-16代码单元的序列。
- 顺便说一句:即使只使用 PowerShell 原生命令,这意味着从 文件 读取输入并再次保存它们 可能会导致不同的字符编码,因为一旦(字符串)数据被读入内存,有关原始字符编码的信息就不会被保留,并且在保存时使用的是 cmdlet 的 default 字符编码;虽然此默认编码在 PowerShell (Core) 6+ 中始终是无 BOM 的 UTF-8,但它因 Windows PowerShell 中的 cmdlet 而异 - 请参阅 this answer。
为了向外部程序发送和接收数据(例如您的情况下为Crypt.exe),您需要匹配它们的字符编码;在您的情况下,对于使用原始 byte 处理的 Windows 控制台应用程序,隐含的编码是系统的活动 OEM 代码页。
-
在发送数据时,PowerShell使用$OutputEncoding首选项变量的编码来编码(什么总是被视为文本)数据,在 Windows PowerShell 中默认为 ASCII(!),在 PowerShell (Core) 中默认为(BOM-less)UTF-8。
-
接收端默认被覆盖:PowerShell使用[Console]::OutputEncoding(它本身反映了chcp报告的代码页)用于解码接收到的数据,在 Windows 上,这默认反映活动的 OEM 代码页,包括 Windows PowerShell 和 PowerShell [Core][1]。
要解决您的主要问题,因此您需要将$OutputEncoding 设置为活动的 OEM 代码页:
# Make sure that PowerShell uses the OEM code page when sending
# data to `.\Crypt.exe`
$OutputEncoding = [Console]::OutputEncoding
问题 2:PowerShell在将数据传送到外部程序时,总是将尾随换行符附加到没有新行符的数据上:
即"foo" | .\Crypt.exe不发送($OutputEncoding-encoded bytes表示)"foo"到.\Crypt.exe的stdin,它在Windows上发送"foo`r`n";即,(适用于平台的)换行符序列(Windows 上的 CRLF)会自动且始终附加(除非字符串已经恰好有一个尾随换行符)。
GitHub issue #5974 和this answer 中讨论了这种有问题的行为。
在您的特定情况下,隐式附加的"`r`n" 也受到字节值移位的影响,这意味着第一个Crypt.exe 调用将其转换为-*,导致另一个 "`r`n" 将在数据发送到第二个 Crypt.exe 调用时附加。
最终结果是一个额外的往返换行符(中间-*),加上一个加密的换行符,导致φΩ)。
简而言之:如果您的输入数据有 no 尾随换行符,则您必须从结果中删除 最后 4 个字符(代表往返和无意中加密的换行序列):
# Ensure that .\Crypt.exe output is correctly decoded.
$OutputEncoding = [Console]::OutputEncoding
# Invoke the command and capture its output in variable $result.
# Note the use of the `Get-Content` cmdlet; in PowerShell, `type`
# is simply a built-in *alias* for it.
$result = Get-Content .\test.txt | .\Crypt.exe --decrypt | .\Crypt.exe --encrypt
# Remove the last 4 chars. and print the result.
$result.Substring(0, $result.Length - 4)
鉴于答案顶部显示的调用cmd /c 也有效,这似乎不值得。
PowerShell 如何使用外部程序处理管道数据:
不同于cmd(或类似POSIX的shell,例如bash):
-
PowerShell 不支持管道中的原始字节数据。[2]
-
当与外部程序交谈时,它只知道文本(而它在与PowerShell自己的交谈时传递.NET 对象命令,这是其强大功能的来源)。
具体来说,它的工作原理如下:
-
当您通过管道将数据发送到外部程序(发送到其标准输入流):
-
当您从外部程序(从其标准输出流)捕获/重定向数据时,它总是解码为文本行 em>(字符串),基于 [Console]::OutputEncoding 中指定的编码,默认为 Windows 上的活动 OEM 代码页(令人惊讶的是,在 both PowerShell版本,截至 v7.0-preview6[1])。
-
PowerShell 内部文本使用 .NET System.String type 表示,它基于 UTF-16 代码单元(通常松散但不正确地称为“Unicode”[3])。 p>
上述也适用:
[1] 在 PowerShell (Core) 中,鉴于 $OutputEncoding 已经默认为 UTF-8,值得称赞的是,让 [Console]::OutputEncoding 相同 - 即,活动代码页是在 Windows 上有效地 65001,正如 GitHub issue #7233 中所建议的那样。
[2] 使用来自 文件 的输入,最接近原始字节处理的方法是将文件读取为 .NET System.Byte 数组 使用 Get-Content -AsByteStream (PowerShell (Core)) / Get-Content -Encoding Byte (Windows PowerShell),但您可以进一步处理诸如数组之类的唯一方法是通过管道传递到一个 PowerShell 命令,该命令旨在处理字节数组,或将其传递给需要字节数组的 .NET 类型的 method。如果您尝试通过管道将这样的数组发送到外部程序,每个字节都将作为其十进制字符串表示形式发送到自己的行。
[3] Unicode 是描述“全球字母表”的抽象标准 的名称。在具体使用中,它有多种标准编码,其中以UTF-8和UTF-16最为广泛。