在Windows上,PowerShell错误解释mosquitto_sub输出中的非ASCII字符

3
注意:这个自答问题描述的问题是特定于在Windows上使用Eclipse Mosquitto,它会影响到Windows PowerShell和跨平台PowerShell (Core)版本。
我使用类似以下的mosquitto_pub命令来发布一条消息:
mosquitto_pub -h test.mosquitto.org -t tofol/test -m '{ \"label\": \"eé\" }'

注:需要注意的是,即使在 Powershell 7.1 中仍然需要对 " 字符进行额外的转义,但这实际上是不必要的,但这是一个单独的问题 - 参见this answer

通过 mosquitto_sub 接收该消息时,非 ASCII 字符é被意外地篡改,并打印出Θ

PS> $msg = mosquitto_sub -h test.mosquitto.org -t tofol/test; $msg

{ "label": "eΘ" }  # !! Note the 'Θ' instead of 'é'

为什么会发生这种情况?如何解决问题?
1个回答

1
问题

截至本文撰写时,mosquitto_sub man page没有提到字符编码的问题。但是,在Windows上,mosquitto_sub表现出了非标准行为,它使用系统活动的ANSI代码页来编码其字符串输出,而不是控制台应用程序预期使用的OEM代码页。[1]

似乎也没有任何选项可以允许您指定要使用的编码方式。

PowerShell将外部应用程序的输出解码为.NET字符串,基于存储在[Console] :: OutputEncoding中的编码,默认为OEM代码页。因此,当它看到字符é的ANSI字节表示形式0xe9在输出中时,它将其解释为OEM表示形式,在其中它表示字符Θ(假设活动ANSI代码页是Windows-1252,活动OEM代码页IBM437,例如在美国英语系统中)。您可以按以下方式进行验证:
# 0xe9 is "é" in the (Windows-1252) ANSI code page, and coincides with *Unicode* code point
# U+00E9; in the (IBM437) OEM code page, 0xe9 represents "Θ".
PS> $oemEnc = [System.Text.Encoding]::GetEncoding([int] (Get-ItemPropertyValue HKLM:\SYSTEM\CurrentControlSet\Control\Nls\CodePage OEMCP)); 
    $oemEnc.GetString([byte[]] 0xe9)

Θ   # Greek capital letter theta

请注意,解码为.NET字符串(System.String)的过程中,字符以UTF-16代码单元的形式存储在内存中,基本上是作为组成.NET字符串的System.Char实例的[uint16]值。这样的代码单元可以完整地编码一个Unicode字符,或者 - 对于位于所谓BMP(基本多文种平面)之外的字符 - 作为所谓代理对的一部分,编码Unicode字符的一半。
在这种情况下,这意味着Θ字符以不同的代码点存储,即Unicode代码点:Θ(希腊大写字母theta,U+0398)。

解决方案:

注意: 解决问题的一个简单方法是激活 Windows 10 中的全局 UTF-8 支持,这将同时设置 ANSI 和 OEM 代码页为65001,即 UTF-8。但是,该功能(a)截至本文仍处于测试版状态且(b)具有深远的影响-有关详细信息,请参见this answer
然而,它实际上是最基本的解决方案,因为它还可以使跨平台使用 Mosquitto 正常工作(在类 Unix 平台上,Mosquitto 使用 UTF-8)。

Powershell 在此情况下必须指定要使用的字符编码,可以按以下方式完成:

PS> $msg = & { 
      # Save the original console output encoding...
      $prevEnc = [Console]::OutputEncoding
      # ... and (temporarily) set it to the active ANSI code page.
      # Note: In *Windows PowerShell* - only - [System.TextEncoding]::Default work as the RHS too.
      [Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding([int] (Get-ItemPropertyValue HKLM:\SYSTEM\CurrentControlSet\Control\Nls\CodePage ACP))

      # Now PowerShell will decode mosquitto_sub's output  correctly.
      mosquitto_sub -h test.mosquitto.org -t tofol/test

      # Restore the original encoding.
      [Console]::OutputEncoding = $prevEnc
    }; $msg

{ "label": "eé" }  # OK

注意:Get-ItemPropertyValue cmdlet需要PowerShell 5或更高版本;在早期版本中,可以使用[Console] :: OutputEncoding = [System.TextEncoding] :: Default ,或者如果代码还必须在PowerShell(Core)中运行,则使用 [Console] :: OutputEncoding = [System.Text.Encoding] :: GetEncoding([int](Get-ItemProperty HKLM:\ SYSTEM \ CurrentControlSet \ Control \ Nls \ CodePage ACP)。ACP)

辅助函数Invoke-WithEncoding可以为您封装此过程。您可以直接安装它从Gist 如下所示(我可以向您保证这样做是安全的,但您始终应该检查):

# Download and define advanced function Invoke-WithEncoding in the current session.
irm https://gist.github.com/mklement0/ef57aea441ea8bd43387a7d7edfc6c19/raw/Invoke-WithEncoding.ps1 | iex

这个解决方法可以简化为:
PS> Invoke-WithEncoding -Encoding Ansi { mosquitto_sub -h test.mosquitto.org -t tofol/test }

{ "label": "eé" }  # OK

一个类似的专注于诊断输出的函数是Debug-NativeInOutput,在this answer中讨论。


作为旁注:
虽然 PowerShell 不是这里的问题,但它也可能表现出有问题的字符编码行为。 GitHub issue #7233 提议使 PowerShell(核心)窗口默认使用 UTF-8,以最小化与大多数现代命令行程序的编码问题(但对于 mosquitto_sub 无济于事),this comment 具体阐述了该提议。

[1] 注意,Python也表现出这种非标准行为,但它提供了UTF-8编码作为一种选择,可以通过将环境变量PYTHONUTF8设置为1或者通过v3.7+ CLI选项-X utf8(必须精确指定大小写)来启用。


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