在命令提示符/Windows Powershell(Windows 10)中使用UTF-8编码(CHCP 65001)

105
我一直在命令提示符和Windows Powershell中强制使用chcp 65001,但根据SO和其他社区的问答帖子来看,这似乎是一个危险且效率低下的解决方案。Microsoft是否提供了改进/完整的替代方法来永久保存,而无需手动更改注册表中的chcp 65001?如果没有,是否有公开宣布的时间表或议程来支持将UTF-8用于Windows CLI? 就个人而言,我一直在使用chcp 949进行韩文字符支持,但反斜杠\的奇怪显示以及在几个应用程序(如Neovim)中显示不正确/难以理解的问题,以及不支持949的非韩文字符似乎最近变得越来越成问题。

有趣,谢谢!(最高投票的警告评论已经8年了,我怀疑它们是否仍然适用。) - Tomalak
1
@Tomalak,在Windows 8之前,对控制台的WriteFile返回编码后的UTF-16代码点数,这可能会导致缓冲写入器出现问题,因为它们期望这是写入的UTF-8字节数,而实际上不是。对于从控制台进行的ReadFile,即使在Windows 10中,如果输入代码页设置为UTF-8,则将受到7位ASCII的限制,因为控制台主机conhost.exe中存在错误的假设。在Windows 10中,它将非ASCII字符作为空值("\0")放入缓冲区。在早期版本中,读取成功但读取0字节,看起来像EOF。 - Eryk Sun
2
现代Windows程序应该使用Unicode控制台函数WriteConsoleWReadConsoleW。然后,唯一的限制是控制台本身对Unicode的固有限制,即仅限于基本多语言平面;不支持复杂脚本和组合代码;如果所选字体没有字符的字形,则不支持字体回退。最终,微软可能会更新经典控制台主机,通过切换到基于DirectWrite的实现来消除这些限制,但目前他们(以及开源贡献者)的努力集中在新的Windows终端上。 - Eryk Sun
4个回答

174
注意:
此答案展示了如何在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(打开控制面板中的区域设置) 按照下面的屏幕截图中的说明进行操作。

Control Panel > Region > Administrative

这将同时将系统的活动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为您创建此值:
# Auto-execute `chcp 65001` whenever the current user opens a `cmd.exe` console
# window (including when running a batch file):
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 部分。

4
将控制台的输入代码页设置为UTF-8会限制通过ReadFile读取的旧程序只能使用7位ASCII输入。(在Windows 8之前输出将会出现问题,但是Windows 7已经接近EOL了。)如果您将系统区域设置为UTF-8,建议将"HKEY_CURRENT_USER\Console%SystemRoot%_system32_cmd.exe"(以及其他感兴趣的窗口标题)的“CodePage”值设置为旧版OEM代码页,以便旧的非Unicode控制台应用程序可以在您的语言环境下正常工作。不要使用chcp.com 65001,除非是在批处理脚本中临时使用,例如对于for /f循环。 - Eryk Sun
3
PowerShell和CMD使用控制台的Unicode API,因此这些控制台代码页设置仅涉及外部控制台应用程序运行时shell设置的输入和输出控制台代码页,而不涉及shell内部的任何内容,例如cmdlets,除非shell在处理文件和管道中的文本时使用输入和输出编码设置。我不确定这些设置在PowerShell中如何与此相关,但是CMD在解码批处理脚本和读取for /f循环中程序的管道输出时使用控制台输出代码页。 - Eryk Sun
2
关于PowerShell:来自外部程序的标准输出根据[console]::OutputEncoding进行解码,通过管道发送到外部程序的文本则基于首选变量$OutputEncoding进行编码。关于文件:Windows PowerShell:读取默认为ANSI,除非存在BOM;使用> / Out-File时写入默认为UTF-16LE,使用Set-Content时默认为ANSI;幸运的是,PowerShell Core在所有这些情况下都一致地使用无BOM的UTF-8。 - mklement0
5
我理解得对吗?现在是公元2021年。而世界上最大的操作系统默认情况下不处理文本文件的UTF-8格式,而是使用类似于1990年代的Latin-1 - ISO-8859-1或其他8位编码吗? - Frank N
1
@bluuf,总的来说,转移到Windows终端可能是个好主意,但我很惊讶它对你不起作用了。你是全局改成UTF-8了吗?还是使用了$OutputEncoding = [console]::InputEncoding = [console]::OutputEncoding = New-Object System.Text.UTF8Encoding技术?问题只是一个_显示_问题(所选字体不支持的字符),还是真正的数据误解? - mklement0
显示剩余10条评论

6
您可以将命令chcp 65001放入Powershell配置文件中,这样每次打开Powershell时都会自动运行该命令。但是对于cmd.exe无效。
微软正在开发一个改进的终端,它将具有完整的Unicode支持。它是开源的,如果您使用的是Windows 10版本1903或更高版本,则已经可以下载预览版
或者,您可以使用第三方终端仿真器,如Terminus

1
不幸的是,在 PowerShell 会话中运行 chcp 65001无效 的,因为 .NET 在启动时缓存控制台的输出编码;此外,Windows PowerShell(但不是 PowerShell _Core_)需要设置 $OutputEncoding - mklement0
1
截至今天(我不知道何时更改),chcp 65001 在 cmd.exe 中可用。我在 Windows 10 Home 20H2 上安装了 Windows Terminal 1.10.2714.0,体验与 Windows PowerShell(5.1)和 cmd.exe 相同(对于我的简单输出 UTF-8 字符的目的)。有趣的是,PowerShell Core 7.1.5 完全失效。新安装的 Power Shell Core 7.1.5 根据属性声称使用代码页 65001,但表现为使用 437。chcp 报告 437,如果运行 chcp 65001,则报告 437,但这对编码没有实际影响。 - WD40
1
@WD40,chcp 65001cmd.exe 中一直有效,但在 PowerShell 中调用时却无效。到今天为止,PowerShell(Core)仍然默认使用 OEM 代码页,例如 437;只有 $OutputEncoding 首选项变量设置为 UTF-8,它控制要用于发送到外部程序的数据的编码。要获得完整的 UTF-8 支持,您需要使用 $OutputEncoding = [console]::InputEncoding = [console]::OutputEncoding = New-Object System.Text.UTF8Encoding。所有这些都在被接受的答案中涵盖了。 - mklement0
@mklement0 感谢您的澄清。但是根据我的阅读和理解,这个答案使得它完全错误。chcp 65001 对于 cmd.exe 是有作用的,而且...如果放在配置文件中,可能对 PowerShell 也有作用。我对 PowerShell 一无所知。 - WD40
@WD40,是的,当从cmd.exe调用时,“chcp 65001”始终有效,而且当从PowerShell中调用cmd.exe时也是如此。然而,它对于PowerShell本身(及其内部命令,即cmdlet)不起作用,因为PowerShell所构建的.NET缓存编码,因此不会选择更改的代码页。$OutputEncoding = ...可以解决这个问题:它告诉PowerShell使用UTF-8代码页,并更新控制台的代码页,也就是说,它也像“chcp 65001”一样起作用。 - mklement0

2

每次启动命令提示符时键入一些命令(例如chcp)可以通过编辑注册表来完成。这是正确的方法,因为它在CMD /?中有记录:

If /D was NOT specified on the command line, then when CMD.EXE starts, it looks for the following REG_SZ/REG_EXPAND_SZ registry variables, and if either or both are present, they are executed first.

HKEY_LOCAL_MACHINE\Software\Microsoft\Command Processor\AutoRun

    and/or

HKEY_CURRENT_USER\Software\Microsoft\Command Processor\AutoRun

现在是2023年,好消息是,使用Windows终端不需要编辑注册表或创建额外的批处理文件。在Windows终端中,转到设置>配置文件,找到命令提示符,然后将命令行%SystemRoot%\System32\cmd.exe(默认)更改为%SystemRoot%\System32\cmd.exe /K "chcp 65001"。这很简单。


谢谢,但是这个方法在每个会话开始时都会打印Active code page: 65001,如果你试图通过>$null来抑制它,chcp命令对于[Console]::OutputEncoding是无效的,因为它会被缓存为原始的代码页。即使你不使用>$null或者| Out-Null,如果任何$PROFILE文件中包含了被捕获、重定向或者抑制的外部程序调用,问题仍然可能发生。简而言之,不幸的是,这不是一种稳健的方法(在Windows PowerShell中,你还需要设置$OutputEncoding)。 - undefined

0
PowerShell ISE可以完美地显示韩文。以下是一个使用utf8编码的示例文本文件,可正常工作:
PS C:\Users\js> cat .\korean.txt

The Korean language (South Korean: 한국어/韓國語 Hangugeo; North 
Korean: 조선말/朝鮮말 Chosŏnmal) is an East Asian language
spoken by about 77 million people.[3]

自从Windows 10的每个版本都配备了ISE,我不认为它已过时。我不同意删除我的原始答案的人。
ISE有一些限制,但是可以使用外部命令进行一些脚本编写:
echo 'list volume' | diskpart # as admin
cmd /c echo hi

编辑:

如果你使用的是Windows 10 1903,你可以从Microsoft Store https://devblogs.microsoft.com/commandline/introducing-windows-terminal/下载Windows Terminal,这样韩文就能在其中正常显示。Powershell 5需要使用UTF8带BOM或UTF16格式的文本。

编辑2:

似乎最理想的方案是使用Windows Terminal + Powershell 7或VSCode + Powershell 7,这样既可以粘贴字符,也可以输出。

编辑3:

即使在编辑2的情况下,一些Unicode字符仍无法粘贴,例如(U+21C6)或unicode spaces。只有在Osx中使用PS7才能正常工作。


1
ISE当然是一个强大的工具,但有些操作无法仅通过ISE完成。例如,我使用Neovim与PowerShell终端,这在ISE中不是可用选项。 - Paul Kim
2
ISE是运行PowerShell脚本的环境。它不支持交互式控制台应用程序(例如diskpart.exe、python.exe shell)。问题在于它隐藏了控制台并将进程输出(但不是输入)重定向到管道。大多数控制台应用程序在文件为管道时切换到完全缓冲。此外,交互式应用程序需要从stdin读取,而从隐藏的控制台窗口无法实现。 (可以通过“ShowWindow”取消隐藏,但单独的输入窗口很笨拙。) - Eryk Sun
js2010:一位主持人删除了你的回答,我猜想可能是因为它被标记为低质量答案,因为它没有提供解释。我会重新发布与您的答案一起被删除的评论,但是要补充@eryksun的观点,在他们对我的回答发表评论的基础上:如果您仅限于使用PowerShell本地命令,您永远不需要担心代码页问题-无论是在控制台还是在ISE中。当您与_外部(控制台)应用程序_交互时,代码页很重要,而ISE甚至比控制台更差。 - mklement0
1
@mklement0,PowerShell在重定向到管道和文件时会插入自己作为中间人,因此它用于解码程序输出的编码非常重要。但这不是改变OutputEncoding变量的问题吗?如果它是[console]::OutputEncoding函数的问题,我会觉得很奇怪。无论如何,尝试设置后者将首先失败,因为powershell_ise.exe最初没有控制台。它调用AllocConsole来获取控制台,并在运行外部控制台应用程序之前隐藏窗口。之后我们可以设置[console]::OutputEncoding - Eryk Sun
1
@mklement0,ConEmu或新的Windows终端都是不错的选择。在Windows 10中,我相信两者都利用了新的伪控制台功能,但ConEmu也适用于旧版本的Windows。conhost.exe和现代程序之间Unicode处理的差异是因为conhost.exe基于经典的Windows GDI API,而较新的程序使用DirectWrite API。 - Eryk Sun
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接