PowerShell Core 无法识别 unicode

PowerShell Core not recognising unicode

我有一个简单的 PowerShell Core 脚本:

$Message = [IO.File]::ReadAllText("$PSScriptRoot\русский.txt", [System.Text.Encoding]::Default)
$Message

据我所知,PowerShell Core 默认为 UTF-8 。然而,正如您在输出中看到的那样,在 unicode 字符方面,它实际上比 PowerShell 5.1 更糟糕。

相同的脚本在 PowerShell 5.1 上运行良好

将 "ReadAllText" 更改为

$Message = [IO.File]::ReadAllText("$PSScriptRoot\русский.txt")

什么都不改变(因为它不应该改变,因为它是读取操作的编码,但只是为了清楚 :))。

Windows 使用 Windows 特定的字符编码,称为 cp1252。 要使用 Unicode 字符,您需要 运行 在第一次对该文件执行某些操作之前执行此命令:

chcp 65001 | Out-Null  # set codepage to UTF-8
$Message = [System.IO.File]::ReadAllText("$PSScriptRoot\русский.txt")

chcp 65001 | Out-Null  # set codepage to UTF-8
$Message = Get-Content "$PSScriptRoot\русский.txt"

希望对您有所帮助

文件不包含字符,它们包含字节。要从字节中获取字符,您需要应用一些编码。如果您对相同的字节应用不同的编码,那么您可以在结果中得到不同的字符。

以你的字节串为例:

PS> $ByteArray = [Byte[]]('24-4D-65-73-73-61-67-65-20-3D-20-5B-49-4F-2E-46-69-6C-65-5D-3A-3A-52-65-61-64-41-6C-6C-54-65-78-74-28-22-24-50-53-53-63-72-69-70-74-52-6F-6F-74-5C-F0-F3-F1-F1-EA-E8-E9-2E-74-78-74-22-2C-20-5B-53-79-73-74-65-6D-2E-54-65-78-74-2E-45-6E-63-6F-64-69-6E-67-5D-3A-3A-44-65-66-61-75-6C-74-29-0D-0A-24-4D-65-73-73-61-67-65' -split '-' | % { [Byte]::Parse($_, 'HexNumber') })
PS> [Text.Encoding]::UTF8.GetString($ByteArray)
$Message = [IO.File]::ReadAllText("$PSScriptRoot\�������.txt", [System.Text.Encoding]::Default)
$Message
PS> [Text.Encoding]::GetEncoding(1251).GetString($ByteArray)
$Message = [IO.File]::ReadAllText("$PSScriptRoot\русский.txt", [System.Text.Encoding]::Default)
$Message
PS> [Text.Encoding]::GetEncoding(1252).GetString($ByteArray)
$Message = [IO.File]::ReadAllText("$PSScriptRoot\ðóññêèé.txt", [System.Text.Encoding]::Default)
$Message

读取文件时使用正确的编码很重要。需要注意的一件重要事情是,您的脚本文件使用代码页 1251,而不是 UTF-8。另请注意,根据 UTF-8,字节序列 F0-F3-F1-F1-EA-E8-E9(代表代码页 1251 中的世界 русский)是无效字节序列,因此您将获得七个替换字符 (U+FFFD)。

由于 PowerShell Core 默认使用 UTF-8 并且您的脚本文件没有 BOM 来指示其他方式(尽管没有 BOM 可以使 PowerShell 识别代码页 1251),PowerShell Core 使用 UTF-8 读取您的文件编码,因此它试图访问 �������.txt (你没有)而不是 русский.txt.

如果将脚本更改为写入文件而不是读取文件,您可以很容易地自己观察它。

PS> $ByteArray2 = [Byte[]](91, 73, 79, 46, 70, 105, 108, 101, 93, 58, 58, 87, 114, 105, 116, 101, 65, 108, 108, 84, 101, 120, 116, 40, 34, 36, 80, 83, 83, 99, 114, 105, 112, 116, 82, 111, 111, 116, 92, 240, 243, 241, 241, 234, 232, 233, 46, 116, 120, 116, 34, 44, 32, 91, 68, 97, 116, 101, 84, 105, 109, 101, 93, 58, 58, 85, 116, 99, 78, 111, 119, 41)
PS> # Representing `[IO.File]::WriteAllText("$PSScriptRoot\русский.txt", [DateTime]::UtcNow)` in codepage 1251
PS> [IO.File]::WriteAllBytes("$(Convert-Path .)\write.ps1", $ByteArray2)
PS> .\write.ps1

现在您可以使用原始脚本读回文件了:

PS> [IO.File]::WriteAllBytes("$(Convert-Path .)\asd.ps1", $ByteArray)
PS> .\asd.ps1
01/18/2019 17:13:15

使用 PowerShell Core 调用两个脚本:

PS> pwsh -Command ".\write.ps1; .\asd.ps1"
01/18/2019 17:21:02

如您所见,您的脚本已在 PowerShell Core 中成功执行。如果您浏览当前目录,那么您可以看到,它同时包含 русский.txt�������.txt,并且它们的内容与控制台上打印的内容匹配。

实际上这个问题与 reading/writing 文件无关(脚本文件本身除外)。它可以通过简单的脚本来演示,它只打印字符串文字的字符代码:

PS> $ByteArray3 = [Byte[]](40, 39, 240, 243, 241, 241, 234, 232, 233, 39, 46, 71, 101, 116, 69, 110, 117, 109, 101, 114, 97, 116, 111, 114, 40, 41, 32, 124, 32, 37, 32, 84, 111, 73, 110, 116, 51, 50, 32, 36, 110, 117, 108, 108, 32, 124, 32, 37, 32, 84, 111, 83, 116, 114, 105, 110, 103, 32, 88, 52, 41, 32, 45, 106, 111, 105, 110, 32, 39, 45, 39)
PS> # Representing `('русский'.GetEnumerator() | % ToInt32 $null | % ToString X4) -join '-'` in codepage 1251
PS> [IO.File]::WriteAllBytes("$(Convert-Path .)\test.ps1", $ByteArray3)

在 Windows PowerShell 中调用它会产生一个结果:

PS> .\test.ps1
0440-0443-0441-0441-043A-0438-0439

虽然 PowerShell Core 会产生不同的结果:

PS> pwsh -Command ".\test.ps1"
FFFD-FFFD-FFFD-FFFD-FFFD-FFFD-FFFD

解决该问题的一种方法是使用带 BOM 的 UTF-8,确保 Windows PowerShell 和 PowerShell Core 在读取脚本文件时使用相同的编码。

答案是在假设 [Text.Encoding]::Default.CodePage return 1251 的情况下编写的,因为 OP 似乎就是这种情况。