首页 > 解决方案 > 在 PowerShell 中通过管道传输时,如何确保 Python 打印 UTF-8(而不是 UTF-16-LE)?

问题描述

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

import sys

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

print("Mamma mia.")

当运行 as 时encodingtest.py > test.txttest.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改为运行,但有没有办法确保输出编码程序端?

标签: pythonpowershellutf-8character-encoding

解决方案


PowerShell 从根本上不支持处理来自外部程序的原始输出(字节流) :

  • 它总是将这样的输出解码为文本,使用存储在[Console]::OutputEncoding

  • 解码后,它将使用默认字符编码进行文件输出操作,例如(实际上是cmdlet>的别名),它们是:Out-File>

    • Windows PowerShell(最高 v5.1):“Unicode”,即 UTF-16LE(这就是您所看到的)
    • PowerShell(核心,v6+):无 BOM 的 UTF-8(现在在所有 cmdlet 中一致应用,与 Windows PowerShell 不同)。

换句话说:即使使用只>涉及一个字符解码和重新编码循环,原始编码和结果编码之间没有任何关系。


所以:

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

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

    • 警告:在Windows PowerShell中,您总是会得到一个带有 BOM的 UTF-8 文件(有关解决方法,请参阅此答案)。在PowerShell (Core)中,您将获得一个没有BOM 的文件(默认情况下),但可以选择创建一个带有-Encoding utf8BOM.

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

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

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


或者,调用 viacmd.exe,它确实将原始字节传递给具有以下内容的文件>

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

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


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

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

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

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

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

  • 有关更多信息,请参阅此答案

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

  • 可以更改数据,不仅会干扰向文件发送字节流,还会干扰外部程序之间的管道数据;例如,请参阅此答案

  • 会显着降低性能

从历史上看,当 PowerShell 是仅限 Windows 的 shell 时,这并不是什么大问题,因为 Windows 世界没有很多值得调用的强大 CLI(命令行界面(实用程序)),所以留在 PowerShell 的范围内通常就足够了(尽管存在性能问题)。

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

因此,PowerShell至少应按需支持原始字节流,甚至在检测到数据在两个外部程序之间传输时自动支持。请参阅GitHub 问题 #1908GitHub 问题 #5974


推荐阅读