当我在PowerShell 7.1中执行以下简单脚本时,无论脚本的编码是Latin1还是UTF8,我都会得到(正确的)值3。
'Bär'.length
这让我很惊讶,因为我有一种(显然是错误的)印象,即PowerShell 5.1中的默认编码是UTF16-LE,而PowerShell 7.1中的编码是UTF-8。
因为两个脚本都将表达式求值为3,所以我不得不得出结论,PowerShell 7.1在执行脚本时应用了一些启发式方法来推断脚本的编码
我的结论是正确的吗?这有记录吗?
我有一种(显然是错误的)印象,认为PowerShell 5.1中的默认编码是UTF16-LE,而PowerShell 7.1中是UTF-8。
有两种不同的默认字符编码需要考虑:
-
写入文件时,各种cmdlet(
Out-File
、Set-Content
)和重定向运算符(>
、>>
)使用的默认输出编码。-
此编码在Windows PowerShell(PowerShell版本高达5.1)中的cmdlet之间变化很大,但幸运的是,现在在PowerShell[Core]v6+中始终默认为无BOM的UTF-8-有关详细信息,请参阅此答案。
-
注意:此编码始终与最初读取数据的文件的编码无关,因为PowerShell不保留此信息,并且从不将文本作为原始字节传递-在进一步处理数据之前,PowerShell始终将文本转换为.NET(
[string]
、System.String
)实例。
-
-
默认输入编码,当读取文件时-引擎读取的源代码和
Get-Content
读取的文件,例如,仅适用于没有BOM的文件(因为带有BOM的文件总是可以正确识别)。-
在没有BOM的情况下:
-
Windows PowerShell 假定系统的活动ANSI代码页,例如美国英语系统上的Windows-1252。请注意,这意味着具有不同活动系统区域设置(非Unicode应用程序的设置)的系统可以以不同的方式解释给定文件。
-
PowerShell[Core]v6+ 更明智地假设UTF-8,它能够表示所有Unicode字符,并且其解释不依赖于系统设置。
-
-
请注意,这些都是固定的、确定性的假设-不使用启发式。
-
结果是对于跨版本源代码,使用的最佳编码是UTF-8和BOM这两个版本都能正确识别。
-
对于包含'Bär'.length
的源代码文件:
如果源代码文件的编码被正确识别,则结果总是3
,假定构造了.NET字符串实例([string]
、System.String
),该实例在内存中总是由UTF-16代码单元([char]
、System.Char
)组成,并且假定.Length
计数这些代码单元的数量[1]
将损坏的文件排除在图片之外(例如没有BOM的UTF-16文件,或者BOM与实际编码不匹配的文件):
.Length
不返回3
的唯一情况是:
-
在Windows PowerShell中,如果文件保存为不带BOM的UTF-8文件。
- 由于ANSI代码页使用固定宽度的单字节编码,UTF-8字节序列中的每个字节都被单独(错误)解释为一个字符,并且由于
ä
(拉丁字母a WITH DIAERESIS,U+00E4
)在UTF-8、0xc3
和0xa4
中被编码为2字节,因此生成的字符串具有4字符 - 因此,字符串呈现为
Bär
- 由于ANSI代码页使用固定宽度的单字节编码,UTF-8字节序列中的每个字节都被单独(错误)解释为一个字符,并且由于
-
相比之下,在PowerShell[Core]v6+中,基于活动ANSI(或OEM代码)页保存的无BOM文件(例如,Windows PowerShell中的
Set-Content
)会导致所有非ASCII字符(在8位范围内)被视为无效字符,因为它们不能被解释为UTF-8。- 所有此类无效字符都简单地替换为
�
(REPLACEMENT CHARACTER,U+FFFD
)-换句话说:信息丢失 - 因此,字符串呈现为
B�r
-并且其.Length
仍然是3
- 所有此类无效字符都简单地替换为
[1]单个UTF-16编码单元能够直接编码Unicode的所谓BMP(基本多语言平面)中的所有65K字符,但对于该平面之外的字符,成对的编码单个Unicode字符。结果:.Length
并不总是返回个字符的计数,特别是表情符号不返回;例如,'👋'.length
是2
编码与这种情况无关:您调用的是string.Length
,它被记录为返回UTF-16代码单元的数量。这大致与字母相关(当你忽略组合字符和像表情符号这样的高代码点时)
只有在隐式或显式转换为字节数组、文件或p/invoke时,编码才会发挥作用。它不会影响.Net存储支持字符串的数据的方式。
谈到PS1文件的编码,这取决于版本。旧版本的回退编码为Encoding.ASCII
,但将遵循UTF-16或UTF-8的BOM。较新的版本使用UTF-8作为后备。
至少在5.1.19041.1中,加载文件'Bär'.Length
(27 42 C3 A4 72 27 2E 4C 65 6E 67 74 68
)并使用. .Bar.ps1
运行它将导致4次打印。
如果将同一文件保存为Windows-1252(27 42 E4 72 27 2E 4C 65 6E 67 74 68
),则它将打印3。
tl;dr:string.Length
总是返回UTF-16代码单元的数目。PS1文件应为UTF-8,并带有BOM,以实现跨版本兼容性。
我认为如果没有BOM,PS 5假设ansi或windows-1252,而PS 7假设utf8没有BOM。这个在记事本中保存为ansi的文件在PS5中有效,但在PS7中并不完美。就像一个带有特殊字符的utf8无bom文件在PS5中无法完美工作一样。utf16 ps1文件将始终具有BOM或编码签名。内存中的powershell字符串总是utf16,但除了表情符号之外,字符的长度被认为是1。如果你有emacs,esc-x hexl模式是一个很好的方式来看待它
'¿Cómo estás?'
format-hex file.ps1
Label: C:Usersjsfoofile.ps1
Offset Bytes Ascii
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
------ ----------------------------------------------- -----
0000000000000000 27 BF 43 F3 6D 6F 20 65 73 74 E1 73 3F 27 0D 0A '¿Cómo estás?'��