注意:
此答案展示了如何在Windows控制台(终端)中切换字符编码为(无BOM)UTF-8系统范围(代码页65001),以便shell(如cmd.exe和PowerShell)在与具有完全Unicode支持的外部(控制台)程序进行通信时正确编码和解码字符(文本),在cmd.exe中也适用于文件I/O。[1]
相比之下,如果您关心的是控制台窗口中Unicode字符呈现的限制方面,请参阅
this answer的中间和底部部分,其中还讨论了替代的控制台(终端)应用程序。
微软是否提供了一个改进/完整的替代方案来替代chcp 65001,并且可以永久保存而无需手动更改注册表?
至少从Windows 10版本1903开始,您可以选择将系统区域设置(非Unicode程序的语言)设置为UTF-8,但是该功能在撰写本文时仍处于测试阶段,并且具有深远的影响。
要激活它,请执行以下操作:
运行intl.cpl(打开控制面板中的区域设置)
按照下面的屏幕截图中的说明进行操作。
这将同时将系统的活动OEM和ANSI代码页设置为65001,即UTF-8代码页。因此,这样做会使所有未来使用OEM代码页的控制台窗口默认为UTF-8(就像在cmd.exe窗口中执行了chcp 65001一样),并且还会使使用ANSI代码页的传统非Unicode GUI子系统应用程序使用UTF-8。
注意事项:
- 如果您使用的是Windows PowerShell,这也会使Get-Content和Set-Content等上下文中的Windows PowerShell默认使用系统的活动ANSI代码页,特别是从无BOM文件中读取源代码,默认为UTF-8(PowerShell Core(v6+)始终如此)。这意味着,在没有-Encoding参数的情况下,ANSI编码的无BOM文件(这在历史上很常见)将被错误读取,并且使用Set-Content创建的文件将是UTF-8而不是ANSI编码。
- 同样,传统的(非Unicode)非控制台应用程序将错误解释ANSI编码的文件。
- 选择一个TrueType字体,但它们通常只支持所有字符的子集,因此您可能需要尝试特定字体,以查看您关心的所有字符是否被表示-有关详细信息,请参阅此答案,该答案还讨论了具有更好的Unicode渲染支持的替代控制台(终端)应用程序。
- 正如eryksun指出的那样,不“支持”UTF-8的传统控制台应用程序将被限制为仅ASCII输入,并且在尝试输出ASCII范围之外的字符时会产生错误的输出。(在过时的Windows 7及以下版本中,程序甚至可能崩溃)。如果运行传统控制台应用程序对您很重要,请参阅eryksun在评论中的建议。
然而,对于Windows PowerShell来说,这还不够:
- 您还必须将$OutputEncoding首选项变量设置为UTF-8:$OutputEncoding = [System.Text.UTF8Encoding]::new();最简单的方法是将该命令添加到您的$PROFILE(仅当前用户)或$PROFILE.AllUsersCurrentHost(所有用户)文件中。
- 幸运的是,在PowerShell Core中不再需要这样做,它在内部始终默认为无BOM的UTF-8。
如果在您的环境中无法将系统区域设置设置为UTF-8,那么请使用启动命令代替。
注意:上述关于传统控制台应用程序的注意事项同样适用于此处。如果运行传统控制台应用程序对您很重要,请参考eryksun在评论中的建议。
对于PowerShell(两个版本),请将以下行添加到您的$PROFILE(仅当前用户)或$PROFILE.AllUsersCurrentHost(所有用户)文件中,这相当于chcp 65001,并补充设置首选变量$OutputEncoding,以指示PowerShell通过UTF-8将数据发送到外部程序的管道:
请注意,从PowerShell会话内部运行chcp 65001是无效的,因为.NET在启动时缓存了控制台的输出编码,并且不知道后来使用chcp进行的更改;另外,正如所述,Windows PowerShell需要设置$OutputEncoding-有关详细信息,请参见此答案的链接1。
$OutputEncoding = [console]::InputEncoding = [console]::OutputEncoding = New-Object System.Text.UTF8Encoding
例如,这里是一种快速而简单的方法,可以以编程方式将此行添加到
$PROFILE
中:
'$OutputEncoding = [console]::InputEncoding = [console]::OutputEncoding = New-Object System.Text.UTF8Encoding' + [Environment]::Newline + (Get-Content -Raw $PROFILE -ErrorAction SilentlyContinue) | Set-Content -Encoding utf8 $PROFILE
对于
cmd.exe
,可以通过注册表定义一个自动运行命令,在键
HKEY_CURRENT_USER\Software\Microsoft\Command Processor
(仅限当前用户)或
HKEY_LOCAL_MACHINE\Software\Microsoft\Command Processor
(所有用户)的值
AutoRun
中:
例如,您可以使用PowerShell为您创建此值:
Set-ItemProperty 'HKCU:\Software\Microsoft\Command Processor' AutoRun 'chcp 65001 >NUL'
可选阅读:为什么一般情况下不建议使用Windows PowerShell ISE:
虽然ISE相比控制台具有更好的Unicode渲染支持,但一般来说它并不是一个好的选择:
首先,ISE已经过时了:它不支持
PowerShell (Core) 7+,未来的所有开发都将在此进行,并且它不是跨平台的,不像新的主要IDE
Visual Studio Code,它已经默认支持PowerShell Core的UTF-8,并且可以配置为支持Windows PowerShell。
ISE通常是用于开发脚本的环境,而不是用于在生产环境中运行脚本(如果您为其他人编写脚本,应该假设它们将在控制台/Windows终端中运行);特别是,关于运行代码,ISE的行为与常规控制台/Windows终端的行为不同:
- 对于运行外部程序的支持较差,不仅因为缺乏对交互式程序的支持(见下一点),还因为:
- 字符编码:
- ISE错误地假设外部程序默认使用ANSI代码页,而实际上是OEM代码页。例如,默认情况下,这个简单的命令,试图将从cmd.exe回显的字符串传递出去,会出现故障(请参见下面的修复方法):
`cmd /c echo hü | Write-Output`
- `$OutputEncoding`首选项变量默认为UTF-8,而不是传统的OEM代码页(与常规控制台相同),并且不适当地在传递给外部程序的(第一个)字符串之前添加了UTF-8 BOM - 请参见
this answer。
- 不适当地将stderr输出渲染为PowerShell错误 - 请参见
this answer。
- ISE使用“点操作符”来调用脚本文件,而不是在“子作用域”中运行它们(后者是在常规控制台窗口/Windows终端中发生的);也就是说,在ISE中,重复的调用在“同一个作用域”中运行。这可能会导致微妙的错误,其中上一次运行留下的定义可能会影响后续的运行。
正如
eryksun所指出的,ISE不支持运行需要用户输入的交互式外部控制台程序。
问题在于它隐藏了控制台并将进程输出(但不是输入)重定向到一个管道。大多数控制台应用程序在文件是管道时会切换到完全缓冲。此外,交互式应用程序需要从标准输入读取,而从隐藏的控制台窗口中无法实现(可以通过ShowWindow取消隐藏,但单独的输入窗口不太方便)。
如果你可以接受这个限制,为了与外部程序进行正确的通信,将活动代码页切换到65001(UTF-8)需要一个笨拙的解决方法:
首先,你必须通过从内置控制台运行任何外部程序(例如chcp)来强制创建隐藏的控制台窗口,你会看到一个控制台窗口闪烁一下。
只有在这之后,你才能像上面所示那样设置[console]::OutputEncoding(和$OutputEncoding)为UTF-8(如果隐藏的控制台尚未创建,你将收到一个“句柄无效”的错误)。
[1] 在PowerShell中,如果你从不调用外部程序,你就不需要担心系统区域设置(活动代码页):PowerShell本地命令和.NET调用总是通过UTF-16字符串(本地.NET字符串)进行通信,并且在文件I/O中应用默认编码,这些编码与系统区域设置无关。同样,因为Windows API函数的Unicode版本用于打印到控制台和从控制台读取,非ASCII字符总是正确打印(在控制台的渲染限制内)。
相比之下,在cmd.exe中,系统区域设置对文件I/O很重要(使用<和>重定向,但特别是对于批处理文件源代码要假设的编码),而不仅仅是在内存中与外部程序通信时(例如在for /f循环中读取程序输出)。
[2] 在 PowerShell v4- 中,静态方法
::new()
不可用,可以使用
$OutputEncoding = (New-Object System.Text.UTF8Encoding).psobject.BaseObject
。请参阅
GitHub issue #5763 了解为什么需要
.psobject.BaseObject
部分。
WriteFile
返回编码后的UTF-16代码点数,这可能会导致缓冲写入器出现问题,因为它们期望这是写入的UTF-8字节数,而实际上不是。对于从控制台进行的ReadFile
,即使在Windows 10中,如果输入代码页设置为UTF-8,则将受到7位ASCII的限制,因为控制台主机conhost.exe中存在错误的假设。在Windows 10中,它将非ASCII字符作为空值("\0")放入缓冲区。在早期版本中,读取成功但读取0字节,看起来像EOF。 - Eryk SunWriteConsoleW
和ReadConsoleW
。然后,唯一的限制是控制台本身对Unicode的固有限制,即仅限于基本多语言平面;不支持复杂脚本和组合代码;如果所选字体没有字符的字形,则不支持字体回退。最终,微软可能会更新经典控制台主机,通过切换到基于DirectWrite的实现来消除这些限制,但目前他们(以及开源贡献者)的努力集中在新的Windows终端上。 - Eryk Sun