关于unpack()和printf()

时间:2016-04-19 21:03:25

标签: 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)的答案为“是”,为什么我见过的大多数例子都使用unpack()

4)在上面的三行代码段中,围绕unpack()的括号是必要的(将它们留下会导致语法错误)。相反,在该示例中,unpack()不需要括在括号中(但是如果它们被添加则不会有害)。有人能解释一下原因吗?

编辑/更新以回复ikegami的回答:

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

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标志,但保持字符串的字节不变。带有v标志的sprintf()输出不同的结果,仅依赖于字符串的UTF8标志,即使字符串的字节保持不变。

1 个答案:

答案 0 :(得分:6)

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

  1. 如果对于两个相同的字符串,sprintf '%vX'根据UTF8标志更改其输出,则会认为它受到 Unicode Bug 的影响。这些问题的大部分实例都已修复(尽管sprintf从未遇到此错误)。