【问题标题】:How to read unbuffered UTF-8 in Perl如何在 Perl 中读取无缓冲的 UTF-8
【发布时间】:2013-06-29 13:45:51
【问题描述】:

我正在尝试以非缓冲方式读取 Perl 中的 UTF-8 输入(即,一旦数据可用,就应该返回它):

die if !binmode STDIN, ':unix:utf8';
my $i;
my $buf;
while ($i = read(STDIN, $buf, 8192)) {
  print "$i\n";
}

但是,如果输入包含 UTF-8 字符分割,则它不起作用:

$ perl -e '$|=1;print"\xc3";sleep 1;print"\xa1";sleep 1;print"AB"' | perl t.pl

这应该先打印 1,然后再打印 2,但它会打印 3,因此即使第一个字符可用,缓冲也会保留它。

在 Perl 中有一个简单的解决方案吗?或者也许是另一种 Unix 脚本语言?

【问题讨论】:

  • 我认为你不应该混合使用 :translators 和二进制 read。首先read你的缓冲区,然后Encode::decode它。正是因为字节字符串"\xc3" 不是UTF-8 字符。
  • 如果您正在读取 utf8 流,则没有“不必要的缓冲”。您必须至少有一个 6 到 8 字节的缓冲区,这样您就不会读取不完整的字符(这就是 perl 在您的示例中所做的)。 那是,因为我在谈论代码点。字形完全是另一回事。
  • 您可以很好地执行binmode STDIN, ':raw'; binmode STDOUT, ':raw',因为您没有对缓冲区进行任何其他处理。即使是不完整的 utf8 字符也会在管道的另一侧重新组装。
  • @Massa:我正在处理,但示例代码中没有说明。
  • @Massa:如果我在示例中将 AB 更改为 ABCDEFGHIJKLMNOP,Perl 仍然会在一批中返回所有内容。因此缓冲区大小大于 8 个字节。并且有不必要的缓冲,因为没有不必要的缓冲,我的示例应该打印 1 和 2,但它会打印 3。

标签: perl utf-8 buffering


【解决方案1】:

首先,您需要将read 更改为sysreadread 一直读取,直到它具有请求的字符数,而 sysread 在数据可用时立即返回。

但是一旦到达就返回数据意味着您最后可能有一个不完整的 UTF-8 字符,因此您必须只解码完全接收的字符并缓冲其余字符。

sub decode_utf8_partial {
   my $s = decode('UTF-8', $_[0], Encode::FB_QUIET);
   return undef
      if !length($s) && $_[0] =~ /
         ^
         (?: [\x80-\xBF]
         |   [\xC0-\xDF].
         |   [\xE0-\xEF]..
         |   [\xF0-\xF7]...
         |   [\xF8-\xFF]
         )
      /xs;

    return $s;
}

binmode($fh);

my $buf;
while (1) {
   my $rv = sysread($fh, $buf, 64*1024, length($buf));
   die $! if !defined($rv);
   last if !$rv;

   while (1) {
      # Leaves undecoded part in $buf    
      my $s = decode_utf8_partial($buf);
      die "Bad UTF-8" if !defined($s);
      last if !length($s);

      ... do something with $s ...
   }
}

【讨论】:

  • 感谢您的建议,这是迄今为止最好的。如果输入有效,它会进行无缓冲解码。如果输入无效(而不是不完整),我无法使此失败并出现错误。
  • 在 :unix 句柄上读取通常不会重新读取,因此 sysread 无关紧要。在这种情况下, :utf-8 层搞砸了。
  • 没有收到任何错误是因为您使用Encode::FB_QUIET 明确要求它。如果您在文件末尾,您可以使用Encode::FB_CROAK 来解决这个问题。但实际上,Encode 应该能够通过将无效与不完整区分开来处理这个更清洁的问题。
  • @Leon Timmermans,不,那不行。从错误发生到文件末尾,您将在 $buf 中累积。 (而且在循环之外执行die if length($buf) 会更简单。)
  • @Leon Timmermans,read 可以在数据可用时阻塞,所以这就是使用 sysread 的原因。这是两者之间的唯一区别。既然他想要sysread 的行为,他应该使用sysread。我不同意你的评论,说没关系。
【解决方案2】:

在 utf-8 模式下,read 对部分字符进行重试。不过,这种方式会破坏您对 read-on-:unix 的特殊使用。我想这是“不要这样做”的情况。

在这种特殊情况下,getc 可能有用。这将读取最低限度的必要条件。在其他情况下,事后解码可能是更好的选择。

【讨论】:

  • 是的,getc 确实不做任何缓冲,但它调用 read(2) 为每次调用读取一个字节,这使得它太慢了。
【解决方案3】:

这似乎可行,尽管您几乎可以肯定想要进入睡眠状态(可能是 Time::HiRes::sleep)或选择进入循环:

die if !binmode STDIN, ':unix:utf8';
use IO::Handle;
die unless STDIN->blocking(0);
my $i;
my $buf;
while (1) {
    $i = read(STDIN, $buf, 8192);
    if ($i) {
        print "$i\n";
    }
    elsif (defined $i) {
        last;
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-27
    • 2011-09-08
    • 2016-10-23
    • 2018-05-13
    • 2016-03-15
    • 1970-01-01
    相关资源
    最近更新 更多