在 Windows 上,PowerShell 错误解释了 mosquitto_sub 输出中的非 ASCII 字符
On Windows, PowerShell misinterprets non-ASCII characters in mosquitto_sub output
注意:这个自答问题描述了一个特定于使用 Eclipse Mosquitto on Windows, where it affects both Windows PowerShell and the cross-platform 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 'é'
- 为什么会这样?
- 我该如何解决这个问题?
问题:
虽然 mosquitto_sub
man page 在撰写本文时没有提及 字符编码 ,但似乎 在 Windows mosquitto_sub
表现出 非标准行为 因为它使用系统的活动 ANSI 代码页 对其字符串输出进行编码,而不是控制台应用程序预期使用的 OEM 代码页。[1]
似乎也没有 选项 可以让您指定 使用什么编码。
PowerShell 根据 [Console]::OutputEncoding
中存储的编码将外部应用程序的输出解码为 .NET 字符串,默认为 OEM 代码页。因此,当它在输出中看到字符 é
、0xe9
的 ANSI 字节表示时,它会将其解释为 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
) that invariably happens means that the characters are stored as UTF-16 code units in memory, essentially as [uint16]
values underlying the System.Char
个构成 .NET 字符串的实例。这样的代码单元编码 Unicode character 要么是完整的,要么 - 对于字符 outside 所谓的 BMP(基本多语言平面)- half 的 Unicode字符,作为所谓的 代理对 .
的一部分
在手头的例子中,这意味着 Θ
字符被存储为 不同的 代码点,即 Unicode代码点:Θ
(希腊大写字母theta,U+0398
)。
解法:
注意:解决该问题的一个简单方法是激活系统范围对 UTF-8 的支持(在 Windows 10),它将 both ANSI 和 OEM 代码页设置为 65001
,即 UTF-8。但是,此功能 (a) 在撰写本文时仍处于 beta 阶段,并且 (b) 具有深远的影响 - 详情请参阅 。
然而,它是最基本的解决方案,因为它也使跨平台 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
或者,如果代码必须 also 运行 在 PowerShell (Core) 中,[Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding([int] (Get-ItemProperty HKLM:\SYSTEM\CurrentControlSet\Control\Nls\CodePage ACP).ACP)
辅助函数Invoke-WithEncoding
可以为你封装这个过程。你可以直接安装它 from a 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
,在 中讨论。
顺便说一句:
虽然 PowerShell 不是这里的问题,但它也可能表现出有问题的字符编码行为。
GitHub issue #7233 proposes making PowerShell (Core) windows default to UTF-8 to minimize encoding problems with most modern command-line programs (it wouldn't help with mosquitto_sub
, however), and this comment 充实提案。
[1] 请注意 Python 也表现出这种非标准行为,但它提供 UTF-8 编码作为选择加入,通过将环境变量 PYTHONUTF8
设置为1
,或通过 v3.7+ CLI 选项 -X utf8
(必须准确指定大小写!)。
注意:这个自答问题描述了一个特定于使用 Eclipse Mosquitto on Windows, where it affects both Windows PowerShell and the cross-platform 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 'é'
- 为什么会这样?
- 我该如何解决这个问题?
问题:
虽然 mosquitto_sub
man page 在撰写本文时没有提及 字符编码 ,但似乎 在 Windows mosquitto_sub
表现出 非标准行为 因为它使用系统的活动 ANSI 代码页 对其字符串输出进行编码,而不是控制台应用程序预期使用的 OEM 代码页。[1]
似乎也没有 选项 可以让您指定 使用什么编码。
PowerShell 根据 [Console]::OutputEncoding
中存储的编码将外部应用程序的输出解码为 .NET 字符串,默认为 OEM 代码页。因此,当它在输出中看到字符 é
、0xe9
的 ANSI 字节表示时,它会将其解释为 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
) that invariably happens means that the characters are stored as UTF-16 code units in memory, essentially as [uint16]
values underlying the System.Char
个构成 .NET 字符串的实例。这样的代码单元编码 Unicode character 要么是完整的,要么 - 对于字符 outside 所谓的 BMP(基本多语言平面)- half 的 Unicode字符,作为所谓的 代理对 .
在手头的例子中,这意味着 Θ
字符被存储为 不同的 代码点,即 Unicode代码点:Θ
(希腊大写字母theta,U+0398
)。
解法:
注意:解决该问题的一个简单方法是激活系统范围对 UTF-8 的支持(在 Windows 10),它将 both ANSI 和 OEM 代码页设置为 65001
,即 UTF-8。但是,此功能 (a) 在撰写本文时仍处于 beta 阶段,并且 (b) 具有深远的影响 - 详情请参阅
然而,它是最基本的解决方案,因为它也使跨平台 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
或者,如果代码必须 also 运行 在 PowerShell (Core) 中,[Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding([int] (Get-ItemProperty HKLM:\SYSTEM\CurrentControlSet\Control\Nls\CodePage ACP).ACP)
辅助函数Invoke-WithEncoding
可以为你封装这个过程。你可以直接安装它 from a 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
,在
顺便说一句:
虽然 PowerShell 不是这里的问题,但它也可能表现出有问题的字符编码行为。
GitHub issue #7233 proposes making PowerShell (Core) windows default to UTF-8 to minimize encoding problems with most modern command-line programs (it wouldn't help with mosquitto_sub
, however), and this comment 充实提案。
[1] 请注意 Python 也表现出这种非标准行为,但它提供 UTF-8 编码作为选择加入,通过将环境变量 PYTHONUTF8
设置为1
,或通过 v3.7+ CLI 选项 -X utf8
(必须准确指定大小写!)。