在 Powershell 中显示 Unicode

Displaying Unicode in Powershell

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

我想显示文件的完整路径,有些文件名中有阿拉伯文、中文、日文和俄文字符

我总是得到一些无法辨认的输出,如下图所示

在控制台中看到的输出正在被另一个脚本使用。 输出包含 ? 而不是实际字符。

执行的命令是

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

是否有任何简单的方法来启动 powershell(通过命令行或以任何可以写入脚本的方式)以便正确查看输出。

P.S。我在 Stack Overflow 上遇到过许多类似的问题,但其中 none 除了将其称为 Windows 控制台子系统问题之外还有很多意见。

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

注意:对于单独的 PowerShell 命令,只有 字体 的选择很重要,假设您的 source-code 文件编码正确;关于 外部程序 $OutputEncoding[Console]::InputEncoding[Console]::OutputEncoding 也很重要。


PowerShell Core (v6+) 视角(请参阅下一节了解 Windows PowerShell),不考虑字符 渲染 问题(也在下一节中介绍),关于与外部程序通信:

  • Unix-like 平台,PowerShell Core 使用 UTF-8 默认(通常,现在,鉴于现代Unix-like平台使用基于UTF-8的语言环境)。

  • Windows上,是遗产系统区域设置,通过其OEM 代码页,确定所有控制台中的默认编码,包括Windows PowerShell 和PowerShell Core控制台 windows,尽管 Windows 10 的最新版本现在允许将系统区域设置设置为代码页 65001 (UTF-8);请注意,该功能在撰写本文时仍处于测试阶段,使用它会产生 far-reaching 后果 - 参见 .

    • 如果您确实使用该功能,PowerShell Core 控制台 windows 将自动支持 UTF-8,尽管在 Windows PowerShell 你仍然需要将 $OutputEncoding 也设置为 UTF-8(在 Core 中已经默认为 UTF-8),如下所示。

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


使您的 Windows PowerShell 控制台 window Unicode (UTF-8) 可识别:

  • 选择一种 TrueType (TT) 字体 支持特定的 scripts(书写系统,字母表)您希望在控制台中正确显示其字符:

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

    • 要更改字体,请单击 window 和 select Properties 的 top-left 角的图标,然后更改为Fonts 选项卡和 select 感兴趣的 TrueType 字体。

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

    • console window的代码页必须切换到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 的最新版本现在允许 (从 Window 10 版本 1903 开始​​,该功能仍处于测试阶段),这使得 all 控制台 windows 默认为 UTF-8,包括 Windows PowerShell 的。
如果您确实使用该功能,则不再严格需要设置 [console]::InputEncoding / [console]::OutputEncoding,但您仍然必须设置 $OutputEncoding(这在 PowerShell Core,其中 $OutputEncoding 已经默认为 UTF-8)。

重要:

  • 这些 设置假定您与之通信的任何外部实用程序都期望 UTF-8 编码输入并产生 UTF-8 输出

    • CLI 编写于例如,Node.js 满足该标准。
    • Python 脚本 - 如果编写时考虑到 UTF-8 支持 - 也可以处理 UTF-8。
  • 相比之下,这些设置可以破坏(较旧的)只需要single-byte编码的实用程序 由系统的遗留 OEM 代码页暗示。

    • 直到 Windows 8.1,这甚至包括标准 Windows 实用程序,例如 find.exefindstr.exe,它们已在 Windows 10 中修复。
    • 请参阅此 post 的底部,了解如何通过切换到 UTF-8 临时地、按需 来绕过此问题以调用给定的实用程序。
  • 这些设置仅适用于外部程序并且与PowerShell 的 cmdlet 的编码无关] 在输出上使用:

    • 有关 PowerShell cmdlet 使用的默认字符编码,请参阅 ;简而言之:如果您希望 Windows PowerShell 中的 cmdlet 默认为 UTF-8(PowerShell [Core] v6+ 无论如何) ,将 $PSDefaultParameterValues['*:Encoding'] = 'utf8' 添加到您的 $PROFILE,但请注意,这将影响在您的会话中使用 -Encoding 参数对 cmdlet 的所有调用,除非该参数被显式使用;另请注意,在 Windows PowerShell 中,您总是会得到带有 BOM 的 UTF-8 文件 ;相反,在 PowerShell [Core] v6+ 中,默认为 BOM-less UTF-8(两者都没有 -Encoding 和使用 -Encoding utf8,您必须使用 'utf8BOM'

可选的背景信息

感谢他的所有意见eryksun

  • 当 TrueType 字体处于活动状态时,console-window 缓冲区 正确保留 (non-ASCII) 个 Unicode 字符。即使他们没有正确渲染;也就是说,即使它们 可能 通常显示为 ?,以表明当前字体不支持,您可以 复制并粘贴 其他地方的此类字符不会丢失信息,如 eryksun 所述。

  • PowerShell 能够输出 Unicode 字符 到控制台 即使没有首先切换到代码页 65001.
    但是,它本身不能保证其他程序可以正确处理此类输出 - 见下文。

  • 当谈到通过stdout外部程序通信时(piping),PowersShell 使用 $OutputEncoding 首选项变量 中指定的字符编码, 默认为 ASCII( !) 在 Windows PowerShell 中,这意味着任何 non-ASCII 字符都被音译为 literal ? 字符,导致 信息丢失。 (相比之下,值得称赞的是,PowerShell Core (v6+) 现在始终使用 (BOM-less) UTF-8 作为默认编码。)

    • 相比之下,传递 non-ASCII 参数(而不是标准输出(管道)输出) 到外部程序似乎需要 no 特殊配置 (我不清楚为什么会这样);例如,以下 Node.js 命令正确 returns €: 1 即使使用默认配置:
      node -pe "process.argv[1] + ': ' + process.argv[1].length" €
  • [Console]::OutputEncoding:

    • 控制当控制台将程序输出转换为控制台显示字符时假定的字符编码。
    • 告诉PowerShell从外部程序捕获输出时采用什么编码。
      结果是,如果您需要从生成 UTF-8 的程序中 捕获输出 ,您还需要将 [Console]::OutputEncoding 设置为 UTF-8;设置 $OutputEncoding 仅涵盖 input(对外部程序)方面。
  • [Console]::InputEncoding 设置键盘输入到控制台的编码[ 2] 并且还确定 PowerShell's CLI 如何解释它通过 stdin(标准输入)接收的数据。

  • 如果无法将整个会话的控制台切换到UTF-8,您可以这样做暂时,对于给定的调用:

      # 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
    
  • 旧版本 Windows(W10 之前)的问题:

    • 65001 的活动 chcp 值破坏了一些外部程序的控制台输出,甚至在旧版本 Windows 中通常的批处理文件可能最终源于一个错误WriteFile() Windows API 函数(也被标准 C 库使用),它错误地报告了 个字符 而不是 bytes,代码页 65001 生效,如 this blog post 中所述
  • 根据 bobince on this answer 2008 年的评论,由此产生的症状是:“我的理解是,将 return 称为 number-of-bytes(例如fread/fwrite/etc) 实际上 return 一个 number-of-characters。这会导致各种各样的症状,例如 input-reading 不完整、在 fflush 中挂起、损坏的批处理文件等等。” =69=]


本机 Windows 控制台(终端)的高级替代品,conhost.exe

eryksun 建议使用两个 替代本机 Windows 控制台 windows (conhost.exe),哪个提供商 更好并且更快的 Unicode 字符 rendering,由于使用了现代的 GPU-accelerated DirectWrite/DirectX API 而不是“旧的 GDI 实现 [that ] 无法处理复杂的脚本、non-BMP 个字符或自动后备字体。"

  • 微软自己的,open-sourceWindows Terminal, which is distributed and updated via the Microsoft Store in Windows 10 - see here求介绍。

  • Long-established third-party 备选方案 ConEmu,它的优点是可以处理较旧的 Windows版本也是。


[1] 请注意, 中的 运行 chcp 65001 PowerShell 会话 不是 有效,因为 .NET 缓存 控制台在启动时的输出编码并且不知道以后使用 chcp 所做的更改(仅选择直接通过 [console]::OutputEncoding] 进行的更改向上)。

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

从不同的 Unicode 子范围阐述 . For testing purposes, I have created some folders and files with valid names 如下:

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

另一方面,使用 SimSun 控制台字体时,会显示(不太明显的)替换符号而不是阿拉伯语和希伯来语字符,而 CJK 字符似乎可以正确显示:

请注意,所有替换符号仅显示,而真实字符会被保留,如下面的 Copy&Paste 所示,来自 PowerShell控制台:

PS D:\PShell> (Get-ChildItem 'D:\bat\UnASCII Names\' -Dir).Name
Arabic (عَرَبِيّ‎)
CJK (中文(繁體))
Czech (Čeština)
Greek (Γρεεκ)
Hebrew (עִבְרִית)
Japanese (日本語)
MathBoldScript ()
Russian (русский язык)
Türkçe (Türkiye)
‹angles›
☺☻♥♦

为了完整起见,以下是 Enable More Fonts for the Windows Command Prompt 的适当注册表值(这也适用于 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     : *新宋体

Powershell ISE 是一个显示外来字符的选项:korean.txt 是一个 UTF8 编码的文件:

PS 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]

我在使用亚马逊翻译服务时遇到了类似的挑战。我从 Windows 商店安装了终端,它现在对我有用了!

如果您安装 Microsoft's "Windows Terminal" from the Microsoft Store(或预览版),它会预先配置为完整的 Unicode 本地化。

您仍然无法输入带有特殊字符的命令...除非您使用 WSL!

刚刚注册只是为了澄清为什么“Lucida Console”作为字体在 Powershell ISE 中工作的困惑。不幸的是,由于缺少声誉,我无法发表评论,所以这里作为答案:

在普通的powershell 中,所有字符都以配置的字体显示。这就是为什么中文或西里尔字符被“Lucida Console”和许多其他字体破坏。 对于中文字符,Powershell ISE 会自动将字体更改为“DengXian”。

您可以通过将它们复制到 Word 或能够显示不同字体的类似程序来找出您的特殊字符使用的替代字体。