【问题标题】:What is the standard test in Perl to determine if a value is an encoded string or a sequence of bytes?Perl 中确定一个值是编码字符串还是字节序列的标准测试是什么?
【发布时间】:2025-11-23 23:20:03
【问题描述】:

Perl 中确定一个值是字节序列还是编码字符串的标准测试是什么?如果它是一个编码字符串,它是什么字符编码的?

让我们假设以下完整的 Perl 脚本:

'foo';

如何确定这个文字字符串是字节序列还是某种编码的字符串?而如果是某种字符编码的字符串,那它是用什么字符编码的呢?

这个问题与 Unicode 或 UTF-8 无关。通常是关于 Perl 中的字节与字符。这个问题也不是关于自动字符编码检测,这完全是一个不同的话题。

更新

在初始化$letter 之后,我希望Perl 告诉我它认为存储在变量$letter 中的字母在什么字符编码中。我不希望它一定是正确。确保 Perl 理解字母的编码字符是我作为程序员的责任。我明白了。但是应该有一种简单易行的方法来测试 Perl 认为一个字符(或字符串)的字符编码是什么。不是吗?

C:\>perl -E "$letter = 'Ž'; say $letter =~ m/\w/ ? 'matches' : 'does not match'"
does not match

C:\>perl -MEncode -E "$letter = decode('UTF-8', 'Ž'); say $letter =~ m/\w/ ? 'matches' : 'does not match'"
does not match

C:\>perl -MEncode -E "$letter = decode('Windows-1252', 'Ž'); say $letter =~ m/\w/ ? 'matches' : 'does not match'"
matches

C:\>perl -MEncode -E "$letter = decode('Windows-1252', 'Ž'); $letter = encode('Windows-1252', $letter); say $letter =~ m/\w/ ? 'matches' : 'does not match'"
does not match

C:\>chcp
Active code page: 1252

C:\>

Perl 不能按需报告它理解(正确或错误地)存储在$letter 中的值是什么字符编码?

【问题讨论】:

  • @innaM 你可能有兴趣阅读我在 PerlMonks 上的 recent post 关于这个 Stack Overflow 问题及其许多很好的答案。我已经读了很多遍了。

标签: perl character-encoding


【解决方案1】:

与其他一些编程语言(例如 Python)不同,Perl 不区分“字节字符串”和“Unicode 字符串”。所有字符串都具有 Unicode 语义,以及字节语义。

话虽如此,包含 ASCII、ISO8859-1 或二进制数据的字符串与包含 Unicode 数据的字符串之间存在纯内部区别。这种区别是使用 UTF8 标志进行的,可以使用 utf8::is_utf8() 函数进行检查。但是,请记住,此标志会自动设置和清除——例如,将非 ISO-8859-1 字符(例如,)附加到字符串会将字符串中的任何数据重新编码为 UTF-8,如有必要,并设置 UTF8 标志。不过,这种转换对于纯 Perl 程序是不可见的,因此您应该很少需要查看它。

如果您有一个非 Unicode 字符串(例如二进制数据)并且您需要弄清楚它的编码方式,请参阅How can I guess the encoding of a string in Perl?

【讨论】:

  • 我知道utf8::is_utf8()Encode::is_utf8(),而且我知道它们是报告纯内部标志状态的函数。我的问题与 Perl 的字符串内部表示的任何方面都没有关系。我非常明确地询问标准测试是什么,用于确定 Perl 是否将对任何给定字符串使用字符语义或字节语义。你说“Perl 不区分'字节字符串'和'Unicode 字符串'”,但事实并非如此,否则Encode::decode()Encode::encode() 是干什么用的?
  • Encode::find_encoding 的文档没有表明有任何方法可以使用它来确定非 Unicode 字符串(即二进制数据)的编码方式。给定编码的名称,它返回与具有该名称的编码相对应的 object。 (我不知道 Perl 程序员会出于什么目的使用这个函数。)
  • @Jim Monty,没有“字节语义”和“字符语义”之类的东西。处理文本的 Perl 函数(例如 ucm//)总是需要 Unicode 代码点的字符串。由于历史原因留下了一些错误,导致\s 有时匹配 NBSP,有时不匹配。 (\w 和 U+0080..U+00FF 中的字母类似。)这是基于 is_utf8 的结果。
  • @ikegami,当 Cwd::getcwd 或任何其他函数返回时,字符串有什么编码,具体取决于操作系统的默认编码,尤其是 Windows 操作系统?在这种情况下,Perl 会考虑什么编码?那么,为什么将这些字符串与 UTF8 中的文字字符串串联会返回一个编码损坏的字符串?
  • @Aleksey F.,Windows:"cp".Win32::GetACP() 返回的编码。请参阅 Win32::LongPath 以了解返回解码文本的版本,不限于任何代码页。 /// Unix: 文件名是任意字节序列,可以有任何编码,也可能根本不是文本。
【解决方案2】:

cp1252中的“Ž”是8E,所以你认为'Ž'chr(0x8E)是一样的。

牢记这一点和以下几点,

decode('UTF-8', chr(0x8E))     ===   chr(0xFFFD)  [Invalid UTF-8]
decode('cp1252', chr(0x8E))    ===   chr(0x17D)
encode('cp1252', chr(0x17D))   ===   chr(0x8E)
  1. 您的第一个 sn-p 将 0x8E 传递给匹配运算符。 U+008E (SINGLE SHIFT TWO) 不是“单词”代码点。

    您所看到的是将 Unicode 代码点(cp1252 编码的文本)以外的内容传递给期望 Unicode 代码点的运算符的效果。

  2. 您的第二个 sn-p 将 0xFFFD 传递给匹配运算符。 U+FFFD(替换字符)不是“单词”代码点。

    您所看到的是将 UTF-8 编码文本(cp1252 编码文本)以外的内容传递给需要 UTF-8 的函数的效果。

  3. 您的第三个 sn-p 将 0x017D 传递给匹配运算符。 U+017D(带有 CARON 的拉丁文大写字母 Z)是一个“单词”代码点。

  4. 您的第四个 sn-p 与您的第一个 sn-p 一样,将 0x8E 传递给匹配运算符。

    您所看到的是将 Unicode 代码点(cp1252 编码的文本)以外的内容传递给期望 Unicode 代码点的运算符的效果。

您的更新实际上展示了以前的答案已经告诉您的内容:匹配运算符始终将字符串视为代码点字符串。无需检查,因为行为始终相同。

(关于“语义”的段落与您的更新无关。由于-E,始终获得正确的行为。)

【讨论】:

    【解决方案3】:

    没有未编码的文件。 Perl 编程语言假定源文件是 Latin-1 或其他格式的。这是一种单字节编码,因此字符和八位字节之间存在 1:1 映射。这意味着在以 UTF-8 编码保存的文件中,

    length("ø") == 2 and
    "ø" eq "\xc3\xb8" and
    "ø" ne "\N{LATIN SMALL LETTER O WITH STROKE}"
    

    use utf8 下所有这些都不正确。

    在 Perl 中,每个字符串实际上都是一个代码点序列。如上所示,没有任何解码步骤,每个八位字节都将被视为一个代码点。这适用于源文件中的字符串文字,以及没有 PerlIO 层的 IO 操作。


    解码和编码

    encode 函数采用一串代码点并使用指定的编码对其进行编码。例如

    use utf8;
    use Test::More; use Encode;
    
    # "is" tests for string equality, "isnt" is the negation
    
    my $str = "ø";
    isnt $str, "\xc3\xb8", "String is unencoded";
    is length($str), 1,    "Unencoded char has length 1";
    
    my $encoded = encode "UTF-8", $str;
    is $encoded, "\xc3\xb8", "The string is properly encoded";
    is length($encoded), 2,  "Encoding may map a codepoint to multiple bytes";
    

    这会发出一串字节,表示为 0x00–0xFF 范围内的代码点。编码的字符串没有可以查询的编码;你,程序员,必须知道。因为它只是一个普通的字符串,我们可以再次对其进行编码:

    my $double_encoded = encode "UTF-8", $encoded;
    is $double_encoded, "\xc3\x83\xc2\xb8", "Double encoding works without type error";
    

    decode 函数采用字节范围内的一串代码点(也称为字节字符串),并根据各自编码的规则对其进行转换。所以:

    is decode("utf8", $double_encoded), $encoded, "Decoding works";
    is decode("utf8", $encoded),        $str,     "Decoding works 2";
    

    它反转编码步骤,从而可能将多个字节范围的字符映射到单个代码点。

    done_testing;
    

    【讨论】:

    • 明白。那么Perl中判断一个值是字节语义还是字符语义的标准test是什么,如果有字符语义,它是用什么字符编码的呢? (我正在寻找一个函数。)
    • 没有这样的事情:每个字符串都被认为是一个代码点序列。如果所有 CP 都 ≤ 0xFF,您可以将其视为字节串。所以/[^\x00-\xFF]/ 可能是一个开始。如果你需要知道一个字符串是字节串还是字符串串,你的 IO 格式可能是可疑的。
    • 在 Perl 中,no 字符串被认为是一个代码点序列,除非并且直到程序员明确执行某些操作以确保 Perl 将字符串视为代码点(即字符)而不是字节。例如,程序员必须使用特定的 I/O 层(例如,:encoding(Windows-1252))或Encode::decode() 或其他一些机制。我的问题是,如何测试 Perl 当前对字符串的理解 状态?是否没有内置函数可以知道字符串是否已被解码?
    • @Jim Monty,这不是真的。在 Perl 中,如果将每个字符串传递给 ucm// 或其他处理文本的函数,则将其视为一个代码点序列。做到这一点是你的工作。字符串函数(例如substrord)不赋予任何意义。
    • @JimMonty 我在我的答案中添加了一个去编码示例。同样,字节串和字符串之间没有语义上的区别,所有字符串都只是裸露的代码点,具有所有未编码的荣耀。编码层指定特定的字符串转换。有些输入视为字节字符串并发出包含更高代码点的字符串。
    【解决方案4】:

    通常是关于 Perl 中的字节与字符。

    这没有任何意义。字符串的每个元素根据定义都是一个字符,所以它肯定是一个字符串。

    字符也可以是字节(8 位值)。这不是非此即彼的事情。

    如何确定这个文字字符串是字节序列还是某种编码的字符串?

    您有一个由字符 66、6F 和 6F 组成的字符串。 Perl 假设如何知道这些值代表什么?它们是 Unicode 代码点吗?它们是使用 UTF-8 编码的 HTML 吗?它们是使用 UTF-8 的配置文件吗?它们是温度传感器测量值吗?它没有办法知道。它们只是三个值。

    【讨论】:

    • 正如我在原始帖子中所解释的,我不是在问有关字符编码检测的问题。我只是想知道在 Perl 的视图中确定某事物是字节还是字符的标准测试是什么。 Perl 非常肯定有观点。
    • 我解释说这是不可能的。 Perl 无法知道 66 是否是一个字节(除非你想要 /^[\x00-\xFF]*\z/。而且我添加了一点,说它总是按照定义是一个字符。
    • Re "Perl 非常肯定有一个视图。",不,Perl 没有为字符串的元素分配语义。
    • 请阅读Byte and Character Semantics in perlunicode 以更好地理解 Perl 的字节和字符语义模型。
    • 我不同意。您应该阅读 amon 和我的回复以获得更好的理解。我敢肯定你来这里不是为了得到引用给你的文档。
    【解决方案5】:

    Perl 缺乏一种简单的方法来了解假定的字符串编码是什么字符。它有一个内部标志,可以通过探测来确定它自己的字符串内部表示是否是 UTF-8,但是这与确定字符串的字符编码的测试完全不同。

    让我们想象一个名为 encoding() 的概念性内置函数。下面是它的作用:

    C:\>perl -E "say encoding 'quick brown fox'"
    ISO-8859-1
    
    C:\>perl -E "use utf8; say encoding 'quick brown fox'"
    UTF-8
    
    C:\>perl -E "use utf8; say encoding 'γρήγορη καφέ αλεπού'"
    UTF-8
    
    C:\>perl -Mutf8 -MEncode -E "say encoding decode('ISO-8859-7', 'γρήγορη καφέ αλεπού')"
    ISO-8859-7
    
    C:\>
    

    (默认字符编码为 ISO-8859-1,也称为拉丁 1。)

    这确实不像其他人认为的那样困难,这正是它的重点。如果 Perl 有一个内置函数来报告分配到字符串的字符编码,它将有助于更容易理解、讨论和处理不同的字符编码。

    【讨论】:

    • 拥有不同编码的字符串根本没有用。 (例如,您将如何连接两个字符串?)您应该始终规范化您的输入并始终重新编码您的输出。在所有语言中都是如此。在 Perl 中,这是通过解码和编码来完成的。
    • 我的问题或答案中没有关于“具有不同编码的字符串”的内容。我知道您“应该始终解码您的输入并始终编码您的输出。”懂这个东西的人都知道。我的问题与解码和编码无关。我问了一个非常简单的问题,答案很简单:Perl 缺少一个函数来报告字符串的理解编码。这太糟糕了,因为它会很有帮助。
    • encoding() 应该总是返回相同的值。否则字符串根本就没有用处。 (例如,您将如何连接两个字符串?)您应该始终规范化您的输入并始终重新编码您的输出。在所有语言中都是如此。在 Perl 中,这是通过解码和编码来完成的。
    • 你真的应该花两秒钟听而不是吹毛求疵。不,用它们的编码标记字符串是没有用的,这样 Perl 可以“理解”(报告)给你。没有语言这样做是有原因的。
    • 它转换字符串,使 8E 变为 17D。这是一个简单的数字映射。没有任何标记。 // 这与$x **= 2; 没有什么不同。该值被转换/映射到另一个,但没有附加标签(“正方形”)。
    最近更新 更多