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

Does PowerShell try to figure out a script's encoding?

当我在 PowerShell 7.1 中执行以下简单脚本时,无论脚本的编码是 Latin1 还是 UTF8,我都会得到(正确的)值 3。

'Bär'.length

这让我感到惊讶,因为我(显然是错误的)印象是 PowerShell 5.1 中的默认编码是 UTF16-LE 而在 PowerShell 7.1 中是 UTF-8。

因为两个脚本都将表达式计算为 3,我不得不得出结论,PowerShell 7.1 在执行脚本时应用了一些启发式方法来推断脚本的编码。

我的结论正确吗?这在某处有记录吗?

编码与这种情况无关:您正在调用 string.Length,它被记录为 return UTF-16 代码单元的数量。这大致与字母相关(当您忽略组合字符和表情符号等高代码点时)

编码仅在隐式或显式转换 to/from 字节数组、文件或 p/invoke 时起作用。它不影响 .Net 如何存储支持字符串的数据。

关于 PS1 文件的编码,这取决于版本。旧版本的回退编码为 Encoding.ASCII,但会遵守 UTF-16 或 UTF-8 的 BOM。较新的版本使用 UTF-8 作为后备。

至少在 5.1.19041.1 中,使用 . .\Bar.ps1 加载文件 'Bär'.Length (27 42 C3 A4 72 27 2E 4C 65 6E 67 74 68) 和 运行 将导致 4 次打印。

如果同一个文件保存为Windows-1252(27 42 E4 72 27 2E 4C 65 6E 67 74 68),那么它会打印3.

tl;dr:string.Length 总是 returns 个 UTF-16 代码单元。 PS1 文件应采用带 BOM 的 UTF-8 格式以实现跨版本兼容性。

我认为没有 BOM,PS 5 假定 ansi 或 windows-1252,而 PS 7 假定 utf8 没有 bom。这个在记事本中保存为 ansi 的文件在 PS 5 中工作,但在 PS 7 中不完美。就像一个 utf8 没有带有特殊字符的 bom 文件在 PS 5 中不能完美工作。 utf16 ps1 文件将始终具有 BOM 或编码签名。内存中的 powershell 字符串始终为 utf16,但除表情符号外,字符的长度被视为 1。如果你有 emacs,esc-x hexl-mode 是一个很好的查看方式。

'¿Cómo estás?'
 format-hex file.ps1

   Label: C:\Users\js\foo\file.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?'��

I was under the (apparently wrong) impression that the default encoding in PowerShell 5.1 is UTF16-LE and in PowerShell 7.1 UTF-8.

有两种不同的默认字符编码需要考虑:

  • 各种cmdlet使用的默认输出编码Out-FileSet-Content ) 和重定向运算符 (>, >>) when writing a file.

    • 此编码 Windows PowerShell[= 中的 cmdlet 中变化很大157=](PowerShell 版本高达 5.1)但现在 - 幸运的是 - 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 with BOM,这两个版本都能正确识别。


至于包含'Bär'.length的源代码文件:

如果 源代码文件的编码被正确识别,结果总是 3,假定 .NET 字符串实例 ([string], System.String)构造出来,在内存中总是由UTF-16编码单元组成([char],System.Char), 假设 .Length 计算这些代码单元的数量。[1]

将损坏的文件排除在外(例如没有 BOM 的 UTF-16 文件,或 BOM 与实际编码不匹配的文件):

.Lengthreturn3的唯一场景是:

  • WindowsPowerShell 中,如果文件保存为 UTF-8 文件没有 BOM.

    • 由于 ANSI 代码页使用固定宽度的单字节编码,因此属于 UTF-8 字节 序列 的每个字节 单独 (错误)被解释为字符,并且由于 ä(带分音符的拉丁文小写字母 A,U+00E4)在 UTF 中被编码为 2 字节-8,0xc30xa4,结果字符串有4个字符。
    • 因此,字符串呈现为 Bär
  • 相比之下,在 PowerShell [Core] v6+ 中,一个基于活动 ANSI(或 OEM 代码)页面保存的无 BOM 文件(例如,在 Windows PowerShell 中使用 Set-Content) 会导致所有非 ASCII 字符(在 8 位范围内)被视为 无效 个字符 - 因为它们不能被解释为 UTF-8。

    • 所有此类无效字符都简单地替换为 (替换字符,U+FFFD) - 换句话说:信息丢失.
    • 因此,字符串呈现为 B�r - 而它的 .Length 仍然是 3

[1] 单个UTF-16编码单元可以直接编码Unicode所谓的BMP(Basic Multi-Lingual Plane)中的所有65K字符,但是对于这个平面之外的字符 代码单元编码单个 Unicode 字符。结果:.Length 并不 总是 return 个字符 的计数,尤其是表情符号;例如,''.length2