代码之家  ›  专栏  ›  技术社区  ›  Adel M.

管道通过CMD和PowerShell时的不同行为和输出

  •  0
  • Adel M.  · 技术社区  · 6 年前

    我试图通过管道将文件内容传输到我制作的一个简单的ASCII对称加密程序。这是一个简单的程序,可以从标准输入中读取输入,并对输入的每个字节加上或减去某个值(224)。 例如:如果第一个字节是4,我们想要加密,那么它就会变成228。如果超过255,程序只执行一些模运算。

    这是我通过cmd获得的输出(test.txt包含“This is a test”):

        type .\test.txt | .\Crypt.exe --encrypt | .\Crypt.exe --decrypt
        this is a test
    

    它也以另一种方式工作,因此它是一种对称加密算法

        type .\test.txt | .\Crypt.exe --decrypt | .\Crypt.exe --encrypt
        this is a test
    

    但是,PowerShell上的行为有所不同。首先加密时,我得到:

        type .\test.txt | .\Crypt.exe --encrypt | .\Crypt.exe --decrypt
        this is a test_*
    

    这就是我第一次解密时得到的结果:

    Screen Shot

    可能是编码问题。提前谢谢。

    0 回复  |  直到 4 年前
        1
  •  17
  •   mklement0    4 年前

    tl;博士 :

    从PowerShell 7.2开始, 如果你需要 原始字节处理 和/或需要防止PowerShell在特定情况下添加 尾随换行符 要删除文本数据,请避免使用 动力壳 整个管道。

    • 将来 支持在外部程序之间传递原始字节数据和文件重定向是本文的主题 GitHub issue #1908 .

    对于原始字节处理,请使用 cmd 具有 /c (在Windows上;在类Unix平台/类Unix Windows子系统上,使用 sh bash 具有 -c ):

    cmd /c 'type .\test.txt | .\Crypt.exe --encrypt | .\Crypt.exe --decrypt'
    

    使用类似的技巧 将原始字节输出保存到 文件 -做 使用 动力壳 是的 > 接线员:

    cmd /c 'someexe > file.bin'
    

    注意 如果你想 捕获外部程序的 文本 输出 在PowerShell变量中 ,你需要确保 [Console]::OutputEncoding 匹配程序的输出字符编码(通常是活动的OEM代码页),在本例中默认为true;详见下一节。

    然而,一般来说, 字节 操纵 文本 最好避免使用数据。


    单独的问题 ,其中只有一个有简单的解决方案:


    问题1 当前位置确实有一个角色 编码问题 ,正如你所怀疑的:

    无形中的PowerShell 在管道中插入自身作为中介,即使在向管道发送数据和从管道接收数据时也是如此 外部程序 :它 将数据从和转换为。网线 ( System.String ),它们是UTF-16代码单元的序列。

    • 顺便提一下:即使只使用PowerShell本机命令,这也意味着从 文件夹 再次拯救他们 可能会导致不同的字符编码,因为在将(字符串)数据读入内存后,原始字符编码的信息不会被保留,而在保存数据时,cmdlet的 违约 使用的字符编码;而这种默认的编码方式始终是BOM较少的UTF-8 PowerShell(核心)6+ ,它因中的cmdlet而异 Windows PowerShell -看到了吗 this answer .

    为了发送和接收数据 外部程序 (例如 Crypt.exe 在你的情况下),你需要匹配 他们的 字符编码 ; 在您的情况下,使用使用raw的Windows控制台应用程序 字节 处理时,隐含的编码是系统的活动OEM代码页。

    • 在…上 发送 数据 ,PowerShell使用 $OutputEncoding 偏好变量 编码 (通常被视为文本)数据,默认为ASCII(!)在Windows PowerShell中,以及在PowerShell(核心)中(无BOM)UTF-8。

    • 这个 接收 终止 默认情况下包括:PowerShell使用 [控制台]::OutputEncoding (这本身反映了 chcp )对于解码接收到的数据,在Windows上,默认情况下,这反映了Windows PowerShell和PowerShell[Core]中的活动OEM代码页 [1] .

    因此,要解决主要问题,您需要 设置 $outpunten编码 至激活的OEM代码页 :

    # Make sure that PowerShell uses the OEM code page when sending
    # data to `.\Crypt.exe`
    $OutputEncoding = [Console]::OutputEncoding
    

    问题2 : 动力壳 总是附加一个尾随的换行符 将数据传输到外部程序时,要删除尚未包含的数据:

    就是, "foo" | .\Crypt.exe 不发送(消息) $outpunten编码 -编码字节(表示) "foo" .\Crypt.exe 是stdin,它发送 "foo`r`n" 在窗户上;i、 例如,一个(适合平台的)换行符序列(Windows上的CRLF)会自动且不变地追加(除非字符串已经碰巧有一个尾随的换行符)。

    这种有问题的行为将在中讨论 GitHub issue #5974 而且在 this answer .

    在您的特定情况下,隐式附加 "`r`n" 也受字节值移位的影响,这意味着 地窖。exe 把它转换成 -* ,导致 另一个 “`r`n” 当数据发送到第二个服务器时附加 地窖。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)
    

    考虑到这个要求 cmd /c 正如答案顶部所示,这似乎不值得。


    PowerShell如何使用外部程序处理管道数据:

    不像 命令 (或类似POSIX的外壳,例如 猛击 ):

    • PowerShell不支持 原始字节数据 在管道中 . [2]
    • 当和 外部程序 它只知道 文本 (尽管它通过了.NET) 物体 当与PowerShell自己的命令交谈时(这是它的大部分功能的来源)。

    具体来说,其工作原理如下:

    • 当你 发送数据 外部程序 通过管道(至其标准数据流):

      • 它是 皈依 文本 (字符串)使用 $outpunten编码 偏好变量 ,默认为ASCII(!)在里面 Windows PowerShell 和(无BOM)UTF-8英寸 PowerShell(核心) .

        • 警告 :如果指定了编码 带BOM表 $outpunten编码 ,PowerShell(从v7.0开始)将 发出BOM表 作为 第一 发送到外部程序的输出行;因此,例如,不要使用 [System.Text.Encoding]::Utf8 在Windows PowerShell中,并使用 [System.Text.Utf8Encoding]::new($false) (事实并非如此)相反。

        • 如果数据是 由PowerShell捕获或重定向,编码问题可能并不总是显而易见,也就是说,如果外部程序以使用 Windows Unicode控制台API 打印到显示屏上。

      • 使用PowerShell的默认输出格式(与打印到控制台时看到的格式相同)将尚未为文本的内容(字符串)字符串化,并使用 重要警告 :

        • 如果(最后一个)输入对象已存在 一个本身没有 尾随换行符 一个是 总是附加 (即使现有的尾随换行符也会被平台原生换行符替换,如果不同的话)。
    • 当你 捕获/重定向数据 从…起 外部程序 (从它的标准流),它总是 解码为 一行行文字 (字符串),基于中指定的编码 [控制台]::OutputEncoding ,默认为Windows上的活动OEM代码页(令人惊讶的是 二者都 PowerShell版本,从v7开始。0-preview6 [1] ).

    • PowerShell内部文本使用表示。网 System.String type ,它基于UTF-16代码单元(通常是松散的,但被错误地称为“Unicode”) [3] ).

    以上 也适用于 :

    • 什么时候 管道数据 之间 外部程序 ,

    • 什么时候 数据是 重定向到文件 ; 也就是说,无论数据来源及其原始字符编码如何,PowerShell都会使用 它的 向文件发送数据时的默认编码;在里面 Windows PowerShell , > 生成UTF-16LE编码的文件(带有BOM),而PowerShell(Core)明智地默认为无BOM的UTF-8(一致地,跨文件编写cmdlet)。


    [1] 在PowerShell(核心)中,考虑到 $outpunten编码 值得称赞的是,它已经默认了UTF-8,因此有必要 [控制台]::OutputEncoding 保持相同-即,活动代码页有效 65001 在Windows上,如中所示 GitHub issue #7233 .

    [2] 有来自 文件 ,最接近原始字节处理的方法是将文件作为 System.Byte 大堆 具有 Get-Content -AsByteStream (PowerShell(核心))/ Get-Content -Encoding Byte (Windows PowerShell),但进一步处理(如阵列)的唯一方法是通过管道连接到 动力壳 用于处理字节数组或将其传递给。网络类型 方法 这需要一个字节数组。如果您试图将这样的数组发送到 外部程序 通过管道, 每个字节将作为其自身行上的十进制字符串表示形式发送 .

    [3] Unicode 摘要的名字是什么 标准 描述“全球字母表”。在具体使用中,它有各种标准 编码 UTF-8和UTF-16是应用最广泛的。