Ade*_* M. 5 powershell encoding cmd pipe
我正在尝试将文件的内容通过管道传输到我制作的一个简单的 ASCII 对称加密程序。这是一个简单的程序,它从 STDIN 读取输入并对输入的每个字节添加或减去某个值 (224)。例如:如果第一个字节是 4,我们要加密,那么它变成 228。如果超过 255,程序只是执行一些模运算。
这是我用 cmd 得到的输出(test.txt 包含“这是一个测试”):
type .\test.txt | .\Crypt.exe --encrypt | .\Crypt.exe --decrypt
this is a test
Run Code Online (Sandbox Code Playgroud)
它也以另一种方式工作,因此它是一种对称加密算法
type .\test.txt | .\Crypt.exe --encrypt | .\Crypt.exe --decrypt
this is a test
Run Code Online (Sandbox Code Playgroud)
但是,PowerShell 上的行为是不同的。首先加密时,我得到:
type .\test.txt | .\Crypt.exe --decrypt | .\Crypt.exe --encrypt
this is a test
Run Code Online (Sandbox Code Playgroud)
这就是我首先解密时得到的:
可能是编码问题。提前致谢。
mkl*_*nt0 10
tl;博士:
如果您需要处理原始字节和/或需要防止 PowerShell 在特定情况下向文本数据添加尾随换行符,请完全避免使用PowerShell管道。
相反,cmd
使用 with /c
(在 Windows 上;在类 Unix 平台/类 Unix Windows 子系统上,使用sh
或bash
with -c
):
cmd /c 'type .\test.txt | .\Crypt.exe --encrypt | .\Crypt.exe --decrypt'
Run Code Online (Sandbox Code Playgroud)
请注意,如果要在 PowerShell 变量中捕获输出,则需要确保它[Console]::OutputEncoding
与.\Crypt.exe
程序的(有效)输出编码(活动 OEM 代码页)相匹配,在这种情况下默认情况下应为 true;有关详细信息,请参阅下一节。
但是,通常最好避免对文本数据进行字节操作。
有两个单独的问题,其中只有一个作为简单的解决方案:
问题1:确实存在字符编码问题,正如您所怀疑的:
PowerShell 以无形的方式将自己插入管道中,即使在向外部程序发送数据和从外部程序接收数据时也是如此:它将数据从 .NET 字符串( System.String
)转换为 .NET 字符串( ),这些字符串是 UTF-16 代码单元的序列。
为了向外部程序发送和接收数据(例如Crypt.exe
在您的情况下),您需要匹配它们的字符编码;在您的情况下,对于使用原始字节处理的 Windows 控制台应用程序,隐含的编码是系统的活动 OEM 代码页。
在发送数据时,PowerShell 使用$OutputEncoding
首选项变量的编码来编码(总是被视为文本的)数据,在 Windows PowerShell 中默认为 ASCII(!),在 PowerShell [Core] 中默认为 (BOM-less) UTF-8。
在接收端通过默认覆盖:PowerShell使用[Console]::OutputEncoding
(其本身反映了代码页报道chcp
),用于解码接收的数据,并在Windows此默认反映了活性OEM代码页,无论是在Windows PowerShell和PowerShell的[核心] [1] .
要解决您的主要问题,因此您需要设置$OutputEncoding
为活动的 OEM 代码页:
# Make sure that PowerShell uses the OEM code page when sending
# data to `.\Crypt.exe`
$OutputEncoding = [Console]::OutputEncoding
Run Code Online (Sandbox Code Playgroud)
问题 2:PowerShell 在将数据通过管道传输到外部程序时,总是在没有换行符的数据上附加一个尾随换行符:
也就是说,"foo" | .\Crypt.exe
不发送($OutputEncoding
表示的-encoded 字节)"foo"
到.\Crypt.exe
的标准输入,它"foo`r`n"
在 Windows 上发送;即,一个(适用于平台的)换行符序列(Windows 上的 CRLF)会自动且不变地附加(除非字符串已经碰巧有一个尾随换行符)。
这个有问题的行为在这个 GitHub 问题和这个答案中都有讨论。
在您的特定情况下,隐式附加"`r`n"
也受字节值移位的影响,这意味着第一个Crypt.exe
调用将其转换为-*
,导致在将数据发送到第二个调用时附加另一个 调用。"`r`n"
Crypt.exe
最终结果是一个额外的往返换行符(中间-*
),加上一个加密的换行符,导致??
)。
简而言之:如果您的输入数据没有尾随换行符,您必须从结果中截去最后 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)
Run Code Online (Sandbox Code Playgroud)
鉴于cmd /c
答案顶部显示的调用也有效,这似乎不值得。
不同于cmd
(或类似 POSIX 的 shell,例如bash
):
具体来说,它的工作原理如下:
当您通过管道(到其标准输入流)将数据发送到外部程序时:
它被转换成文本使用在编码指定的字符(串)$OutputEncoding
的偏好变量,默认为ASCII(!)中的Windows PowerShell,和(BOM-更少)UTF-8中的PowerShell [核心]。
警告:如果您将带有 BOM的编码分配给$OutputEncoding
,PowerShell(从 v7.0 开始)将发出 BOM作为发送到外部程序的第一行输出的一部分;因此,例如,不要[System.Text.Encoding]::Utf8
在 Windows PowerShell 中使用(它发出 BOM),[System.Text.Utf8Encoding]::new($false)
而是使用(它不会)。
如果PowerShell未捕获或重定向数据,则编码问题可能并不总是很明显,即是否以使用Windows Unicode 控制台 API打印到显示器的方式实现外部程序。
使用 PowerShell 的默认输出格式(与您打印到控制台时看到的格式相同)将不是文本(字符串)的内容进行字符串化,但有一个重要的警告:
当您从外部程序(从其标准输出流)捕获/重定向数据时,它总是根据 中指定的编码解码为文本行(字符串),[Console]::OutputEncoding
默认为 Windows 上的活动 OEM 代码页(令人惊讶的是,在两个PowerShell 版本,从 v7.0-preview6 [1] 开始)。
PowerShell 内部文本使用 .NETSystem.String
类型表示,该类型基于 UTF-16 代码单元(通常松散地,但错误地称为“Unicode” [3])。
以上也适用:
在外部程序之间传输数据时,
当数据被重定向到文件时;也就是说,无论数据来源及其原始字符编码如何,PowerShell在将数据发送到文件时都使用其默认编码;在Windows PowerShell 中,>
生成 UTF-16LE 编码的文件(带有 BOM),而 PowerShell [Core] 明智地默认为无 BOM 的 UTF-8(始终如一,跨文件写入 cmdlet)。
添加对在外部程序和文件重定向之间传递原始数据的支持是此 GitHub 问题的主题。
[1] 在 PowerShell [Core] 中,鉴于$OutputEncoding
已经默认为 UTF-8,值得称赞的[Console]::OutputEncoding
是,保持相同是有意义的 - 即,活动代码页65001
在 Windows上有效,如此 GitHub 问题中所建议的。
[2] 使用来自文件的输入,最接近原始字节处理的是使用(PowerShell [Core]) / (Windows PowerShell)将文件作为.NETSystem.Byte
数组读取,但您可以进一步处理此类文件的唯一方法因为数组是通过管道传输到旨在处理字节数组的PowerShell命令,或者通过将其传递给需要字节数组的 .NET 类型的方法。如果您尝试通过管道将这样的数组发送到外部程序,则每个字节都将作为其十进制字符串表示形式在其自己的行上发送。Get-Content -AsByteStream
Get-Content -Encoding Byte
[3] Unicode是描述“全球字母表”的抽象标准的名称。在具体使用中,它有多种标准编码,其中使用最广泛的是UTF-8和UTF-16。
归档时间: |
|
查看次数: |
680 次 |
最近记录: |