如何让 UTF-8 在 Windows 上的新式 PowerShell 中完美运行?

How do I get UTF-8 to work flawlessly in modern PowerShell on Windows?

提问人:cubanpit 提问时间:11/10/2023 最后编辑:cubanpit 更新时间:11/10/2023 访问量:78

问:

我有一个 C++ 程序,它输出原始 UTF-8 并在 Linux 上完美运行,但在 Windows shell 上输出并不那么好。例如®,“”变成“┬«”,“©”变成“┬⌐”。 代码中还有一个 Python 部分,在打印到 shell 时似乎效果更好,所以我尝试测试一下 Python 输出。

PS C:\Users\user> python -c 'print("\N{GREEK CAPITAL LETTER DELTA}")' > test_file_python.txt
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "C:\Users\user\AppData\Local\Programs\Python\Python310\lib\encodings\cp1252.py", line 19, in encode
    return codecs.charmap_encode(input,self.errors,encoding_table)[0]
UnicodeEncodeError: 'charmap' codec can't encode character '\u0394' in position 0: character maps to <undefined>
PS C:\Users\user> python -X utf8 -c 'print("\N{GREEK CAPITAL LETTER DELTA}")' > test_file_python.txt
PS C:\Users\user> cat test_file_python.txt
Δ
PS C:\Users\user> python -c 'print("\N{GREEK CAPITAL LETTER DELTA}")'
Δ
PS C:\Users\user> cat .\test_file_python_wsl.txt  # Generated in WSL with the above commands
Δ
PS C:\Users\user> Format-Hex .\test_file_python.txt

   Label: C:\Users\user\test_file_python.txt

          Offset Bytes                                           Ascii
                 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
          ------ ----------------------------------------------- -----
0000000000000000 E2 95 AC C3 B6 0D 0A                            �ö��

PS C:\Users\user> Format-Hex .\test_file_python_wsl.txt

   Label: C:\Users\user\test_file_python_wsl.txt

          Offset Bytes                                           Ascii
                 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
          ------ ----------------------------------------------- -----
0000000000000000 CE 94 0A                                        ��

我不明白 PowerShell 如何与编码一起工作,Python 如何在写入 shell 时正确地做到这一点,但在重定向时却不能,以及为什么在 WSL 的 Linux Bash 中完美运行的东西在较新的跨平台 PowerShell Core 中会出现此类问题,它应该“正常工作”。 这些是多个问题,但可能有一个共同的答案。

编辑: 我忘了添加一些重要信息,我正在使用带有此编码设置的 PowerShell Core v7.3.6:

PS C:\Users\user> $OutputEncoding

Preamble          :
BodyName          : utf-8
EncodingName      : Unicode (UTF-8)
HeaderName        : utf-8
WebName           : utf-8
WindowsCodePage   : 1200
IsBrowserDisplay  : True
IsBrowserSave     : True
IsMailNewsDisplay : True
IsMailNewsSave    : True
IsSingleByte      : False
EncoderFallback   : System.Text.EncoderReplacementFallback
DecoderFallback   : System.Text.DecoderReplacementFallback
IsReadOnly        : True
CodePage          : 65001
python windows PowerShell 编码 UTF-8

评论

0赞 tripleee 11/10/2023
错误消息表明您的控制台已配置为使用代码页 1252。尝试,但工作方法还取决于您的 Windows 版本。这几乎可以肯定是重复的。chcp 65001
0赞 Walter Mitty 11/10/2023
您是否尝试过在 cat 语句中使用 -encoding utf8?
0赞 cubanpit 11/10/2023
我用更多信息更新了帖子,编码设置似乎已经很好了。
0赞 mklement0 11/10/2023
@tripleee,实际上它是 Python 在做自己的事情,并使用 ANSI 代码页(例如 Windows-1252)而不是控制台应用程序预期使用的 OEM 代码页(例如 CP 437)。 原则上会有所帮助,但不会从 PowerShell 内部,因为 .NET 在其类中缓存编码,PowerShell 使用该编码对子进程的输出进行解码。根据 Python 输出中的字符,使用 ANSI 代码页仍会中断,因此 UTF-8 是唯一可靠的解决方案,不幸的是,这需要在 Python 和 PowerShell 中付出额外的努力。chcp 1252[Console]

答:

2赞 mklement0 11/10/2023 #1

在 Windows 上,有两部分拼图:

  • 必须指示 PowerShell 在与外部程序通信时使用 UTF-8

    • 使用以下魔法咒语(请注意,这是你要做的,不是一个选项,因为 .NET 缓存了存储在 中的编码):chcp 65001cmd.exe[Console]

       $OutputEncoding = [Console]::InputEncoding = [Console]::OutputEncoding = New-Object System.Text.UTF8Encoding
      
    • 有关背景信息,请参阅此答案

  • 还必须指示 Python 使用 UTF-8 I/O(假设 Python v3.7+):

    • 或者: 将(大小写)传递给可执行文件:-X utf8python

      python -X utf8 -c 'print("\N{GREEK CAPITAL LETTER DELTA}")' > test_file_python.txt
      
    • 或者:在调用 Python 之前,运行$env:PYTHONUTF8=1

    • 以上启用了 Python UTF-8 模式,这将成为 Python 3.15 中的默认模式。


通过一次性配置步骤的另一种方法是将计算机切换为在系统范围内使用 UTF-8,在这种情况下,不需要上述步骤;但是,这会产生深远的影响,并且可能会破坏遗留的脚本和应用程序 - 请参阅此答案


背景资料:

PowerShell 在一定程度上是一个很好的 Windows 控制台公民:

  • 它使用控制台窗口的活动代码页隐含的编码(一个用于输入,一个用于输出),默认为系统的旧版 OEM 代码页;具体说来:

    • 解码外部程序的输出时,它使用控制台的输出代码页,如 中的 .NET 中所示,这是外部程序在对其输出进行编码时至少在历史上应使用的内容。[Console]::OutputEncoding

    • 但是,当对输入进行编码以通过管道(目标程序的 stdin 流)提供给外部程序时,PowerShell 会做出一个奇怪的选择;它不使用控制台的活动(输入)代码页,而是使用存储在 $OutputEncoding 首选项变量中的编码,该变量具有意外的默认值:

      • 在 Windows PowerShell(旧版、仅限 Windows、随 Windows 一起提供,其最新版本和最新版本为 v5.1)中,它默认为 ASCII(!)

      • PowerShell (Core) 7+(新式跨平台按需安装版本)中,它默认为 UTF-8(!)。

        • 注意:PowerShell 7+ 读取文件(包括源代码)和写入文件时,内部始终使用(无 BOM)UTF-8,但必须对外部程序的输出进行解码,但必须基于控制台的(输出)代码页。

        • GitHub 问题 #7233 建议通过将控制台代码页设置为 .65001

Python 表现出非标准行为:

  • 当它发现其 stdout 流被重定向时,默认情况下,它使用系统的旧版 ANSI(!) 代码页对其输出进行编码。

  • 直接打印到控制台时,捕获或重定向输出时因误解而导致的问题不会浮出水面,因为 Python 随后使用相关的 Unicode WinAPI 打印到控制台,绕过任何编码问题:

    • 换句话说:Python 的输出在直接输出时始终正确显示,但在将输出重定向到文件、通过 PowerShell 的管道传递输出或在 PowerShell 变量中捕获输出时,可能会发生误解。

评论

0赞 cubanpit 11/10/2023
谢谢,我认为已经足够了,如果使用,添加并且似乎给出了正确的结果。我的理解是,新的 PowerShell Core 默认使用 UTF-8,但似乎并非如此。以这种方式设置编码似乎也可以修复 C++ 程序的原始输出。$OutputEncoding[Console]::OutputEncoding[Console]::InputEncodingpython -X utf8
0赞 mklement0 11/10/2023
很高兴听到它,@cubanpit。PowerShell Core 在内部使用 UTF-8,即在读取文件(包括源代码)和写入文件时,但必须对外部程序的输出进行解码,但外部程序的输出仍必须基于控制台的(输出)代码页。请参阅我的更新,其中还指出了 GitHub 问题,该问题建议使交互式会话也与外部程序一起使用 UTF-8。
1赞 cubanpit 11/10/2023
感谢您提供背景信息和 GitHub 问题的链接,这些都是有价值的材料。