在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.")
当 run as 时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 从根本上不支持处理来自外部程序的原始输出(字节流):
-
它总是解码这样的输出作为文本,使用存储在所述字符编码
[Console]::OutputEncoding- 有关更多信息,请参阅此答案。
-
解码后,它使用其默认字符编码进行文件输出操作,例如
>(实际上是Out-Filecmdlet的别名),它们>是:- 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.
- 警告:在Windows PowerShell 中,您总是会得到一个带有 BOM的 UTF-8 文件(有关变通方法,请参阅此答案)。在PowerShell (Core) 中,您将获得一个没有BOM 的(默认情况下),但可以选择创建一个带有
把它们放在一起(保存和恢复[Console]::OutputEncoding未显示的原始文件):
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new()
encodingtest.py | Set-Content -Encoding utf8 test.txt
[Console]::OutputEncoding如果您已切换到系统范围内的UTF-8 ,则无需修改,如本答案所述,但请注意,在撰写本文时,此 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 的外壳时,这不是什么大问题,因为 Windows 世界没有许多功能强大的 CLI(命令行界面(实用程序))值得调用,因此留在 PowerShell 的范围内通常就足够了(尽管存在性能问题)。
然而,在日益跨平台的世界中,尤其是在类 Unix 平台上,功能强大的 CLI 比比皆是,有时对于高性能操作来说是必不可少的。
因此,PowerShell的应支持至少原始字节流需求,和situationally甚至自动当检测数据被管道两个外部程序之间。请参阅GitHub 问题 #1908和GitHub 问题 #5974。