关于 unpack() 和 printf() 中的 v 标志的 Perl 问题

标签 perl for-loop printf pack unpack

我正在尝试完成以下任务:

对于任意的 Perl 字符串(无论它是否以 UTF-8 内部编码,以及它是否设置了 UTF-8 标志),从左到右扫描字符串,对于每个字符,打印 Unicode 代码点对于十六进制格式的那个字符。让我自己绝对清楚:我不想打印 UTF-8 字节序列或其他东西;我只想为字符串中的每个字符打印 Unicode 代码点。

起初,我想出了以下解决方案:

#!/usr/bin/perl -w

use warnings;
use utf8;
use feature 'unicode_strings';

binmode(STDOUT, ':encoding(UTF-8)');
binmode(STDIN, ':encoding(UTF-8)');
binmode(STDERR, ':encoding(UTF-8)');

$Text = "\x{3B1}\x{3C9}";
print $Text."\n";
printf "%vX\n", $Text;

# Prints the following to the console (the console is UTF8):
# αω
# 3B1.3C9

然后我看了一些例子,但是没有合理的解释,这让我怀疑我的解决方案是正确的,现在我对我自己的解决方案以及例子都有疑问。

1) Perl 关于 (...)printf 中 v 标志的文档说:

“这个标志告诉 Perl 将提供的字符串解释为一个整数向量,字符串中的每个字符一个。[...]”

但是,它没有说明“整数向量”的确切含义。在查看我的示例的输出时,这些整数似乎是 Unicode 代码点,但我希望有确切知道的人确认这一点。

因此问题是:

1) 我们能否确定从字符串中提取的每个整数都是相应字符的 Unicode 代码点(而不是其他一些字节序列)?

其次,关于我发现的一个例子(略有修改;我不记得我从哪里得到的,也许来自 Perl 文档):
#!/usr/bin/perl -w

use warnings;
use utf8;
use feature 'unicode_strings';

binmode(STDOUT, ':encoding(UTF-8)');
binmode(STDIN, ':encoding(UTF-8)');
binmode(STDERR, ':encoding(UTF-8)');

$Text = "\x{3B1}\x{3C9}";
print $Text."\n";
printf "%vX\n", $Text for unpack('C0A*', $Text);

# Prints the following to the console (the console is UTF8):
# αω
# 3B1.3C9

作为一个 C 和汇编人员,我只是不明白为什么有人会写 printf示例中所示的语句。根据我的理解,相应的行在语法上等同于:
for $_ (unpack('C0A*', $Text)) {
  printf "%vX\n", $Text;  
}

据我了解,unpack()需要 $Text , 解压它(无论这意味着什么)并返回一个列表,在这种情况下,该列表具有一个元素,即解压后的字符串。然后 $_ 用一个元素遍历该列表(没有在任何地方使用),因此块(即 printf() )被执行一次。总而言之,上述代码片段所做的唯一操作是执行 printf "%vX\n", $Text;一度。

因此问题是:

2)将其包装到示例中所示的 for 循环中的原因可能是什么?

最后的问题:

3) 如果问题 1) 的答案是"is",为什么我见过的大多数示例都使用 unpack()毕竟?

4) 在上面的三行代码片段中,围绕 unpack() 的括号是必要的(离开它们会导致语法错误)。相比之下,在示例中,unpack()不需要括在括号中(但即使添加它们也无害)。有人可以解释原因吗?

编辑/更新回复下面池上的回答:

当然,我知道字符串是整数序列。但

a) 这些整数有许多不同的编码,某个字符串的内存区域中的字节取决于编码,即如果我有两个包含完全相同字符序列的字符串,但我使用不同的编码将它们存储在内存中,字符串内存位置的字节序列是不同的。

b) 我强烈认为(除了 Unicode)还有许多其他系统/标准将字符映射到整数/代码点。例如,Unicode 代码点 0x3B1 是希腊字母 α,但在其他一些系统中,它可能是德语字母 Ö。

在这种情况下,恕我直言,这个问题是完全合理的,但我可能应该更准确地改写它:

如果我有一个字符串 $Text它只包含 Unicode 代码点的字符,如果我然后执行 printf "%vX\n", $Text; , 是否会在所有情况下以十六进制为每个字符打印 Unicode 代码点,特别是(但不限于):
  • 不考虑 Perl 字符串的实际内部编码
  • 无论字符串的 UTF-8 标志如何
  • 是否use 'unicode_strings'活跃

  • 如果答案是肯定的,那么所有使用 unpack() 的示例有何意义? ,特别是上面的例子?顺便说一句,我现在记得我从哪里得到的:原始形式在 Perl 的 pack() 中。文档,在关于 C0 和 U0 模式的部分。因为他们正在使用 unpack() ,这样做一定有充分的理由。

    编辑/更新第 2 号

    我做了进一步的研究。以下证明UTF8标志起着重要作用:
    use Encode;
    use Devel::Peek;
    
    $Text = "\x{3B1}\x{3C9}";
    Dump $Text;
    printf("\nSPRINTF: %vX\n", $Text);
    print("UTF8 flag: ".((Encode::is_utf8($Text)) ? "TRUE" : "FALSE")."\n\n");
    
    Encode::_utf8_off($Text);
    Dump $Text;
    printf "\nSPRINTF: %vX\n", $Text;
    print("UTF8 flag: ".((Encode::is_utf8($Text)) ? "TRUE" : "FALSE")."\n\n");
    
    # This prints the following lines:
    #
    # SV = PV(0x1750c20) at 0x1770530
    #   REFCNT = 1
    #   FLAGS = (POK,pPOK,UTF8)
    #   PV = 0x17696b0 "\316\261\317\211"\0 [UTF8 "\x{3b1}\x{3c9}"]
    #   CUR = 4
    #   LEN = 16
    #
    # SPRINTF: 3B1.3C9
    # UTF8 flag: TRUE
    #
    # SV = PV(0x1750c20) at 0x1770530
    #   REFCNT = 1
    #   FLAGS = (POK,pPOK)
    #   PV = 0x17696b0 "\316\261\317\211"\0
    #   CUR = 4
    #   LEN = 16
    #
    # SPRINTF: CE.B1.CF.89
    # UTF8 flag: FALSE
    

    我们可以看到_utf_off确实删除了 UTF8 标志,但保持字符串的字节不变。 sprintf() with v 标志输出不同的结果,即使字符串的字节保持不变,也仅取决于字符串的 UTF8 标志。

    最佳答案

    sprintf '%vX'不了解代码点或 UTF-8。它只返回字符串字符的字符串表示。换句话说,

    sprintf('%vX', $s)
    

    相当于
    join('.', map { sprintf('%X', ord($_)) } split(//, $s))
    

    这意味着它输出 s[0] , s[1] , s[2] , ..., s[length(s)-1] , 十六进制,用点分隔。

    无论 UTF8 的状态如何,它都会返回字符串的字符(整数)。旗帜。这意味着字符串的存储方式(例如,是否设置了 UTF8 标志)对输出没有影响。
    use Encopde;
    
    $Text1 = "\xC9ric";
    utf8::downgrade($Text2);
    
    printf("Text1 is a string of %1\$d characters (a vector of %1\$d integers)\n",
       length($Text1));
    print("UTF8 flag: ".((Encode::is_utf8($Text2)) ? "TRUE" : "FALSE")."\n");
    printf("SPRINTF: %vX\n\n", $Text1);
    
    $Text2 = $Text1;
    utf8::upgrade($Text2);
    print($Text1 eq $Text2
        ? "Text2 is identical to Text1\n\n"
        : "Text2 differs from Text1\n\n");
    
    printf("Text2 is a string of %1\$d characters (a vector of %1\$d integers)\n",
       length($Text2));
    print("UTF8 flag: ".((Encode::is_utf8($Text2)) ? "TRUE" : "FALSE")."\n");
    printf "SPRINTF: %vX\n\n", $Text2;
    

    输出:
    Text1 is a string of 4 characters (a vector of 4 integers)
    UTF8 flag: FALSE
    SPRINTF: C9.72.69.63
    
    Text2 is identical to Text1
    
    Text2 is a string of 4 characters (a vector of 4 integers)
    UTF8 flag: TRUE
    SPRINTF: C9.72.69.63
    

    让我们更改您问题中的代码以显示相关信息:
    use Encode;
    
    $Text1 = "\x{3B1}\x{3C9}";
    
    printf("Text1 is a string of %1\$d characters (a vector of %1\$d integers)\n",
       length($Text1));
    printf("SPRINTF: %vX\n\n", $Text1);
    
    $Text2 = $Text1;
    Encode::_utf8_off($Text2);
    print($Text1 eq $Text2
        ? "Text2 is identical to Text1\n\n"
        : "Text2 differs from Text1\n\n");
    
    printf("Text2 is a string of %1\$d characters (a vector of %1\$d integers)\n",
       length($Text2));
    printf "SPRINTF: %vX\n\n", $Text2;
    

    输出:
    Text1 is a string of 2 characters (a vector of 2 integers)
    SPRINTF: 3B1.3C9
    
    Text2 differs from Text1
    
    Text2 is a string of 4 characters (a vector of 4 integers)
    SPRINTF: CE.B1.CF.89
    

    它表明 sprintf '%vX'不同的字符串会有不同的输出,这并不奇怪,因为 sprintf '%vX'简单地输出字符串的字符。您可以很容易地使用 uc而不是 _utf8_off .

  • 如果对于两个相同的字符串,sprintf '%vX'根据 UTF8 更改其输出标志,它将被视为遭受 Unicode 错误。大多数实例都已修复(尽管 sprintf 从未遭受过此错误)。
  • 关于关于 unpack() 和 printf() 中的 v 标志的 Perl 问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36729422/

    相关文章:

    perl - 同时使用 ActivePerl 和 Strawberry

    perl - 如何找到给定目录中最大的 10 个文件?

    regex - 如何在 awk 命令中支持不常见的字符

    C 错误无法将参数 1 从 int * 转换为 int

    arrays - 在遍历数组时为每个元素添加延迟

    Python正则表达式问题

    c - 指向字符串的指针以及赋值和取消引用之间的区别

    regex - Perl 中的正则表达式分组

    c - 为什么 printf ("%d\n"、 printf ("%d\b"、 a)) 以这种方式工作?

    pthreads - 线程 : one printf statement get printed twice in child thread