如何确保Python在PowerShell中通过管道传输时打印UTF8(而不是UTF16LE)?

2024-09-30 00:23:08 发布

您现在位置:Python中文网/ 问答频道 /正文

我希望在管道传输时将文本打印为UTF-8(例如,到文件),因此在Windows 10上通过PowerShell的Python 3.7.3上,我正在执行以下操作:

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,但是有没有办法确保输出编码程序端?


Tags: 文件pytest文本txt字节管道windows
1条回答
网友
1楼 · 发布于 2024-09-30 00:23:08

PowerShell基本上不支持处理来自外部程序的原始输出(一个字节流

  • 它总是使用存储在[Console]::OutputEncoding中的字符编码对输出进行解码,例如文本

  • 解码后,它将默认字符编码用于文件输出操作,例如>(实际上是^{}cmdlet的别名),对于>来说,它是:

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

换言之:即使仅使用>也需要一个字符解码和重新编码周期,原始编码和结果编码之间没有任何关系。


因此:

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

  • 将Python脚本调用的输出通过管道传输到Out-File,或者最好是,如果已知输入已经是字符串(对于外部程序调用始终为true)^{}Encoding utf8

    • 警告:在Windows PowerShell中,您将始终获得一个带有BOM的UTF-8文件(有关解决方法,请参见this answer)。在PowerShell(Core)中,您将获得一个而没有BOM(默认情况下是这样),但可以选择使用-Encoding utf8BOM创建一个

要将其放在一起(保存和恢复未显示的原始[Console]::OutputEncoding):

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

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


或者,通过cmd.exe调用,这将原始字节传递给具有^{的文件:

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

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


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

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

PowerShell中的一切都是通过管道进行调解的,包括使用重定向操作符>,其中... > foo.txt实际上是... | Out-File foo.txt的语法糖

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

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

  • 有关更多信息,请参见this answer

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

  • 能改变数据,不仅干扰sending字节流到文件,但也带有管道数据在/到外部程序之间;有关示例,请参见this answer

  • 会显著降低性能

从历史上看,当PowerShell是一个只支持Windows的shell时,这并不是什么大问题,因为Windows世界没有太多可调用的CLI(命令行界面(实用程序)),因此留在PowerShell的范围内通常就足够了(尽管存在性能问题)

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

因此,PowerShell应至少支持原始字节流随需应变,并且在检测到数据在两个外部程序之间通过管道传输时,甚至可以自动。见GitHub issue #1908GitHub issue #5974

相关问题 更多 >

    热门问题