如何确保 Python 在 PowerShell 中通过管道传输时打印 UTF-8(而不是 UTF-16-LE)?

How to ensure Python prints UTF-8 (and not UTF-16-LE) when piped in PowerShell?

我想在管道传输时将文本打印为 UTF-8(例如,一个文件),所以在 Python 3.7.3 上 Windows 10 通过 PowerShell,我正在做这个:

import sys

if not sys.stdout.isatty():
    sys.stdout.reconfigure(encoding='utf-8')

print("Mamma mia.")

当运行为encodingtest.py > test.txt时,test.txt则为:

00000000  FF FE 4D 00 61 00 6D 00 6D 00 61 00 20 00 6D 00  ÿþM.a.m.m.a. .m.
00000010  69 00 61 00 2E 00 0D 00 0A 00                    i.a.......

奇怪的是,它以 FF FE 开头,这是 UTF-16-LE 的字节顺序标记——空字节打印在字符之间(就像 UTF-16 那样)!但是,当我通过 CMD 而不是 PowerShell 运行 它时,它可以很好地打印 UTF-8。 即使通过 PowerShell 进行管道传输,如何让 Python 打印 UTF-8?

我可以 运行 encodingtest.py | Out-File -Encoding UTF8 test.txt 代替,但是 有没有办法确保程序端的输出编码?

PowerShell 根本不支持处理来自外部程序的 raw 输出(字节 的流:

  • 它总是解码这样的输出text,使用存储在[Console]::OutputEncoding[中的字符编码=39=]

    • 有关详细信息,请参阅
  • 解码后,它使用 默认字符编码进行文件输出操作,例如 >(实际上是 Out-File cmdlet),> 是:

    • Windows PowerShell(最高 v5.1):“Unicode”,即 UTF-16LE(您所看到的)
    • PowerShell(Core,v6+):无 BOM UTF-8(现在 一致地 应用于所有 cmdlet,不同于 WindowsPowerShell).

换句话说:即使只使用 > 也会涉及字符解码和重新编码循环,原始编码和生成的编码之间没有任何关系。


因此:

  • (暂时)设置[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new()

  • 将 Python 脚本调用的输出通过管道传输到 Out-File - 或者,最好是,如果已知输入是 strings 已经(对于外部程序调用总是如此)- Set-ContentEncoding utf8.

    • 注意:在 Windows PowerShell 中,您将总是 得到一个 UTF-8 文件 使用 BOM(请参阅 了解解决方法)。在 PowerShell (Core) 中,你会得到一个 没有 BOM(默认情况下你会这样做),但可以选择创建一个 [= =21=].

将它们放在一起(保存并恢复原始[Console]::OutputEncoding未显示):

[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new()
encodingtest.py | Set-Content -Encoding utf8 test.txt

如果您已切换到 UTF-8 系统范围,则无需修改 [Console]::OutputEncoding,如 中所述,但请注意此 Windows 10 功能在撰写本文时仍处于测试阶段,具有深远的影响。


或者,通过 cmd.exe 调用,确实 将原始字节传递给 >:

cmd /c 'encodingtest.py > test.txt'

此技术(通过 /bin/sh -c 类似地适用于类 Unix 平台)是缺乏原始字节处理的一般解决方法(见下文)。


背景信息:PowerShell 管道中缺乏对原始字节流的支持:

PowerShell 的管道是基于 object 的,这意味着流过它的是 .NET 类型 的实例。传统的仅二进制管道的这种演变是 PowerShell 的强大功能和多功能性的关键。

PowerShell 中的所有内容 都通过管道进行调解,包括使用重定向运算符 >... > foo.txt 实际上是 [=29] 的语法糖=]

  • 对于PowerShell-native命令,它总是输出.NET对象,一些形式的编码是必要的为了以有意义的方式将这些对象写入文件(除非对象已经是字符串,否则原始字节表示没有任何意义),因此 text 基于 PowerShell 的用于显示的表示使用了输出格式化系统(顺便说一句,这就是为什么 > 使用非字符串输入通常不适合为以后的 程序化 处理生成文件的原因)。

  • 对于外部程序,PowerShell 选择仅通过文本(字符串)与它们通信,这如上所述,接收输出涉及将接收到的原始字节不可避免地解码为 .NET 字符串。

  • 有关详细信息,请参阅

缺少对原始字节流的支持是有问题的: 除非你直接调用底层.NET API来显式处理字节流(这会很麻烦),否则解码和重新编码为文本的循环:

  • 可以更改数据,不仅会干扰将字节流发送到文件,还会干扰管道数据between/to 外部程序;有关示例,请参见

  • 会显着降低性能

从历史上看,当 PowerShell 是 Windows-only shell 时,这不是什么大问题,因为 Windows 世界没有很多功能强大的 CLI(命令-line 接口(实用程序))值得调用,因此留在 PowerShell 领域通常就足够了(尽管存在性能问题)。

然而,在日益跨平台的世界中,尤其是在类 Unix 平台上,功能强大的 CLI 比比皆是,有时对于 高性能 操作是不可或缺的。

因此,PowerShell 应该至少按需支持原始字节流,根据情况甚至自动 当检测到数据正在两个外部程序之间通过管道传输时。参见 GitHub issue #1908 and GitHub issue #5974