在PowerShell中显示Unicode

83

我想要实现的目标应该相当简单,尽管PowerShell试图让它变得困难。

我想要显示文件的完整路径,其中一些文件名包含阿拉伯语、中文、日语和俄语字符。

我总是得到一些无法理解的输出,例如下面所示的输出:

Enter image description here

控制台中看到的输出内容会被另一个脚本直接使用。输出内容包含实际字符的 ?

执行的命令是

(Get-ChildItem -Recurse -Path "D:\test" -Include *unicode* | Get-ChildItem -Recurse).FullName

有没有一种简单的方法可以启动PowerShell(通过命令行或以可写入脚本的方式),以便正确地查看输出?

P.S. 我已经在Stack Overflow上浏览了许多类似的问题,但除了称其为Windows控制台子系统问题外,它们都没有太多的输入。


1
你在 PowerShell 控制台中使用什么字体?你确定它包含了所需的语言字符吗? - Jeff Zeitlin
可能与此相关:https://dev59.com/qXI-5IYBdhLWcg3wQWGc - Jeff Zeitlin
如何将其他字体添加到Windows控制台 - jpaugh
7个回答

125
注意:
  • 在Windows系统中,涉及到Unicode字符的渲染时,主要取决于所选择的字体 / 终端应用程序

    • 现在,使用 Windows Terminal是一个不错的替代方案,它自Windows 10以来通过Microsoft Store进行发布和更新,可替换传统控制台主机(由conhost.exe提供的控制台窗口),提供更好的Unicode字符支持。在Windows 11 22H2中,Windows终端甚至成为了默认的控制台(终端)
  • 在与外部程序通信时,涉及到Unicode字符的编程处理时,$OutputEncoding[Console]::InputEncoding[Console]::OutputEncoding也很重要 - 请参见下文。


PowerShell Core(v6+)的观点(有关Windows PowerShell的下一节),与字符渲染问题无关(也在下一节中介绍),关于与外部程序通信:

  • 类Unix平台上,PowerShell Core默认使用UTF-8编码(通常是因为现代的类Unix平台基于UTF-8的本地化环境)。

  • Windows,通过其旧版的系统区域设置,即通过其OEM代码页来确定默认编码,包括所有控制台,包括Windows PowerShell和PowerShell Core控制台窗口,尽管最近的Windows 10版本允许将系统区域设置为代码页65001(UTF-8);请注意,此功能作为本文写作时仍处于测试版,并且使用它会产生深远的后果 - 参见this answer

    • 如果您使用该功能,则PowerShell Core控制台窗口将自动支持UTF-8编码,但在Windows PowerShell中,您仍然需要将$OutputEncoding设置为UTF-8(在Core中已经默认为UTF-8),如下所示。

    • 否则 - 特别是在较旧的Windows版本上 - 您可以使用与下面Windows PowerShell详细介绍的相同方法。


使您的Windows PowerShell控制台窗口支持Unicode(UTF-8):

  • 选择支持您想要在控制台中正确显示的特定脚本(书写系统,字母表)的字符的TrueType(TT)字体:

    • 重要提示:虽然所有 TrueType 字体原则上都支持 Unicode,但它们通常只支持一个子集的 Unicode 字符,即对应于特定脚本(书写系统)的字符,例如拉丁文、西里尔文(俄语)等。
      在您的特定情况下——如果您必须支持阿拉伯语以及中文、日语和俄语字符——您唯一的选择是SimSun-ExtB,它仅在Windows 10上可用。
      请参见Wikipedia,了解 Windows 字体针对哪些脚本(字母表)。

    • 要更改字体,请单击窗口左上角的图标,然后选择 属性,然后切换到 字体 选项卡并选择所需的 TrueType 字体。

  • 此外,为了与外部程序进行正确的通信:

    • 必须将控制台窗口的代码页切换到 65001,即 UTF-8 代码页(通常使用 chcp 65001 完成,但不能直接从 PowerShell 会话中使用[1],但下面的 PowerShell 命令具有相同的效果)。

    • Windows PowerShell 必须指示使用 UTF-8 与外部实用程序进行通信,无论是通过将管道输入发送到外部程序,还是通过它的 $OutputEncoding 首选项变量(在解码来自外部程序的输出时,应用存储在 [console]::OutputEncoding 中的编码)。

在Windows PowerShell中,以下神奇的咒语可以实现这一点(如所述,这 隐含地 执行了chcp 65001):
$OutputEncoding = [console]::InputEncoding = [console]::OutputEncoding =
                    New-Object System.Text.UTF8Encoding

为了使您未来的交互式PowerShell会话默认支持UTF-8,请将上述命令添加到您的$PROFILE文件中,以“持久化”这些设置。
注意:最近的Windows 10版本现在允许将系统区域设置设置为代码页65001(UTF-8)(该功能在Windows 10版本1903中仍处于测试阶段),这将使所有控制台窗口都默认为UTF-8,包括Windows PowerShell的控制台窗口。如果您使用该功能,则不再严格需要设置[console] :: InputEncoding / [console] :: OutputEncoding,但您仍然需要设置$OutputEncoding(在PowerShell Core中不需要设置$OutputEncoding,因为它已经默认为UTF-8)。
重要提示:
  • 这些设置假定你与任何外部实用程序的通信都期望UTF-8编码的输入并产生UTF-8输出。

    • 例如,使用Node.js编写的CLI符合此标准。
    • 如果Python脚本考虑到UTF-8支持,也可以处理UTF-8。
  • 相比之下,这些设置可能会破坏(旧版)只期望单字节编码的实用程序,因为这暗示了系统的传统OEM代码页。

    • 直到Windows 8.1,甚至包括标准的Windows实用程序,如find.exefindstr.exe,这在Windows 10中已被修复。
    • 请参阅本文底部,了解如何通过临时切换到UTF-8来解决此问题,以满足调用给定实用程序的需求。
  • 这些设置仅适用于外部程序,与PowerShell的cmdlet在输出上使用的编码无关

    • 有关PowerShell cmdlet使用的默认字符编码,请参见this answer;简而言之:如果要使Windows PowerShell中的cmdlet默认为UTF-8(PowerShell [Core] v6+无论如何都是这样),请将$PSDefaultParameterValues['*:Encoding'] = 'utf8'添加到您的$PROFILE,但请注意,除非显式使用该参数,否则这将影响所有调用带有-Encoding参数的cmdlet的会话;还请注意,在Windows PowerShell中,您将始终获得带BOM的UTF-8文件;相反,在默认为BOM-less UTF-8的PowerShell [Core] v6+中(在没有-Encoding-Encoding utf8的情况下),您必须使用'utf8BOM'

可选背景信息

eryksun致敬,感谢他的所有贡献。

  • While a TrueType font is active, the console-window buffer correctly preserves (non-ASCII) Unicode chars. even if they don't render correctly; that is, even though they may appear generically as ?, so as to indicate lack of support by the current font, you can copy & paste such characters elsewhere without loss of information, as eryksun notes.

  • PowerShell is capable of outputting Unicode characters to the console even without having switched to code page 65001 first.
    However, that by itself does not guarantee that other programs can handle such output correctly - see below.

  • When it comes to communicating with external programs via stdout (piping), PowersShell uses the character encoding specified in the $OutputEncoding preference variable, which defaults to ASCII(!) in Windows PowerShell, which means that any non-ASCII characters are transliterated to literal ? characters, resulting in information loss. (By contrast, commendably, PowerShell Core (v6+) now uses (BOM-less) UTF-8 as the default encoding, consistently.)

    • By contrast, however, passing non-ASCII arguments (rather than stdout (piped) output) to external programs seems to require no special configuration (it is unclear to me why that works); e.g., the following Node.js command correctly returns €: 1 even with the default configuration:
      node -pe "process.argv[1] + ': ' + process.argv[1].length" €
  • [Console]::OutputEncoding:

    • controls what character encoding is assumed when the console translates program output into console display characters.
    • also tells PowerShell what encoding to assume when capturing output from an external program.
      The upshot is that if you need to capture output from an UTF-8-producing program, you need to set [Console]::OutputEncoding to UTF-8 as well; setting $OutputEncoding only covers the input (to the external program) aspect.
  • [Console]::InputEncoding sets the encoding for keyboard input into a console[2] and also determines how PowerShell's CLI interprets data it receives via stdin (standard input).

  • If switching the console to UTF-8 for the entire session is not an option, you can do so temporarily, for a given call:

      # Save the current settings and temporarily switch to UTF-8.
      $oldOutputEncoding = $OutputEncoding; $oldConsoleEncoding = [Console]::OutputEncoding
      $OutputEncoding = [Console]::OutputEncoding = New-Object System.Text.Utf8Encoding
    
      # Call the UTF-8 program, using Node.js as an example.
      # This should echo '€' (`U+20AC`) as-is and report the length as *1*.
      $captured = '€' | node -pe "require('fs').readFileSync(0).toString().trim()"
      $captured; $captured.Length
    
      # Restore the previous settings.
      $OutputEncoding = $oldOutputEncoding; [Console]::OutputEncoding = $oldConsoleEncoding
    
  • Problems on older versions of Windows (pre-W10):

    • An active chcp value of 65001 breaking the console output of some external programs and even batch files in general in older versions of Windows may ultimately have stemmed from a bug in the WriteFile() Windows API function (as also used by the standard C library), which mistakenly reported the number of characters rather than bytes with code page 65001 in effect, as discussed in this blog post.
  • The resulting symptoms, according to a comment by bobince on this answer from 2008, are: "My understanding is that calls that return a number-of-bytes (such as fread/fwrite/etc) actually return a number-of-characters. This causes a wide variety of symptoms, such as incomplete input-reading, hangs in fflush, the broken batch files and so on."


优秀的替代方案,取代Windows自带控制台(终端)conhost.exe

eryksun提供了两个替代方案,取代原生Windows控制台窗口conhost.exe),它们使用现代的、GPU加速的DirectWrite/DirectX API,提供了更好、更快的Unicode字符渲染,而不是“旧的GDI实现[无法处理复杂的脚本、非BMP字符或自动回退字体]”。

  • 微软自己的开源Windows Terminal,自Windows 10以来通过Microsoft Store分发和更新 - 在这里查看介绍。

  • 历史悠久的第三方替代品ConEmu,它的优点是也适用于旧版本的Windows。


[1] 请注意,在 PowerShell 会话中运行 chcp 65001 是无效的,因为 .NET 在启动时缓存了控制台的输出编码,并且不知道后来使用 chcp 进行的更改(只有直接通过 [console] :: OutputEncoding] 进行的更改才会被捕获)。

[2] 我不清楚这在实践中如何体现;如果您知道,请告诉我们。


@Sharak:这很奇怪,我无法解释(我不使用Vim)。请注意,UTF-16应该对您的配置文件没有问题,就像UTF-8一样-两者都需要BOM(除了在PowerShell_Core中,UTF-8不需要BOM)。 - mklement0
@Sharak:只有在您的配置文件中存在非ASCII字符时,BOM才很重要。如果您的UTF-8文件中没有BOM,则Windows PowerShell将该文件读取为“ANSI”文件,即错误地解释该文件。 - mklement0
1
@Sharak:chcp是为cmd.exe设计的,它是在PowerShell出现之前多年唯一的shell。从PowerShell内部运行chcp不可靠,并且无论如何仍然需要设置$OutputEncoding。你可以认为Windows PowerShell应该提供类似于chcp的命令,但由于某些原因(我不知道),从未引入过这样的命令。 请注意,PowerShell _Core_本地支持UTF-8,因此对于现代(非遗留)程序,它在全球范围内都可以工作,因此未来不再需要这样的命令(尽管有一个支持遗留程序的命令也无妨)。 - mklement0
1
你说得对,虽然在 MS Gothic 字体中反斜杠看起来像只鹿。Consolas 和 Lucida Console 在 ISE 中却能正常工作,真是奇怪。 - js2010
1
非常感谢,我通过在我的ps脚本开头添加以下内容解决了问题: $OutputEncoding = [System.Text.Encoding]::UTF8 - Martin Muñoz
显示剩余10条评论

10
详细说明了亚历山大·马丁的答案。为了测试目的,我创建了一些文件夹和文件,使用了来自不同Unicode子范围的有效名称,如下所示:

Valid names

例如,在使用Courier New控制台字体时,PowerShell控制台中会显示替换符号而不是CJK字符:

Courier New

另一方面,使用SimSun控制台字体时,阿拉伯和希伯来字符显示为(难以辨认的)替换符号,而CJK字符似乎显示正确:

SimSun

请注意,所有替换符号仅仅是显示,而实际字符是被保留的,正如您可以在上面PowerShell控制台中看到的复制&粘贴
(Get-ChildItem 'D:\bat\UnASCII Names\' -Dir).Name

输出:

Arabic (عَرَبِيّ‎)
CJK (中文(繁體))
Czech (Čeština)
Greek (Γρεεκ)
Hebrew (עִבְרִית)
Japanese (日本語)
MathBoldScript ()
Russian (русский язык)
Türkçe (Türkiye)
‹angles›
☺☻♥♦

为了完整起见,这里提供适当的注册表值,以启用Windows命令提示符的更多字体(这同样适用于Windows PowerShell控制台):
(Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Console\TrueTypeFont' |
    Select-Object -Property [0-9]* | Out-String).Split(
        [System.Environment]::NewLine,
        [System.StringSplitOptions]::RemoveEmptyEntries) |
     Sort-Object

示例输出:

0       : Consolas
00      : Source Code Pro
000     : DejaVu Sans Mono
0000    : Courier New
00000   : Simplified Arabic Fixed
000000  : Unifont
0000000 : Lucida Console
932     : *MS ゴシック
936     : *新宋体

如何将NSimSun设置为默认字体,这样每次启动PS时就不需要更改字体了。 - djy
1
@djy 在窗口的左上角点击PowerShell图标,并在默认值下设置字体,而不是在属性下设置,或者如果你右键单击PowerShell图标(快捷方式),则在属性下设置。 - JosefZ

8
如果您从Microsoft Store(或预览版本)安装Microsoft的“Windows终端”,它将预配置为完全支持Unicode本地化。

Windows Terminal Preview with snowman ⛄, Arabic (عَرَبِيّ‎), CJK (中文(繁體)), Czech (Čeština), Greek (Γρεεκ), Hebrew (עִבְרִית), Japanese (日本語), MathBoldScript (), Russian (русский язык), Türkçe (Türkiye), ‹angles›, ☺☻♥♦

除非您使用WSL,否则仍然无法使用特殊字符输入命令!

Using WSL, we are able to run echo "snowman ⛄"


1
我最终使用了这个终端。虽然还有其他一些选择,但是这个支持选项卡和正确的Unicode正是我所需要的。 - Quan To

1

PowerShell ISE是一种显示外文字符的选项:korean.txt是一个UTF-8编码的文件:

cd C:\Users\js
Get-Content korean.txt

输出:

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

1
为了概括一下为什么使用ISE是一个不好的主意(详见此答案底部部分):(a)它已经过时并且不支持PowerShell Core;(b)它是一个“开发”环境,不适用于在生产中运行脚本的最终用户;(c)它不支持交互式控制台应用程序。 - mklement0

1

0
在普通的PowerShell中,所有字符都以配置的字体显示。这就是为什么例如中文或西里尔文字符会在“Lucida Console”和许多其他字体中断开。
对于中文字符,PowerShell ISE会自动更改字体为“等线”。
您可以通过将它们复制到Word或类似程序中来查找用于您特殊字符的替代字体,该程序能够显示不同的字体。

-1
确保您安装了包含所有有问题的字符的字体,并将其设置为Win32控制台字体。如果我没记错的话,请单击窗口左上角的PowerShell图标,然后选择“属性”。弹出的对话框应该有一个选项可以设置使用的字体。它可能必须是位图(.FON.FNT)字体。

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