PowerShell 是否尝试找出脚本的编码?



当我在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-FileSet-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、0xc30xa4中被编码为2字节,因此生成的字符串具有4字符
    • 因此,字符串呈现为Bär
  • 相比之下,在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并不总是返回个字符的计数,特别是表情符号不返回;例如,'👋'.length2

编码与这种情况无关:您调用的是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?'��

最新更新