1

我正在尝试以无缓冲的方式读取 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 脚本语言?

4

3 回答 3

4

首先,您需要从 更改readsysreadread读取直到它具有请求的字符数,而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 ...
   }
}
于 2013-06-29T16:44:58.880 回答
1

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

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

于 2013-06-29T14:13:48.110 回答
0

这似乎可行,尽管您几乎可以肯定想要进入睡眠状态(可能是 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;
    }
}
于 2013-06-30T18:03:12.990 回答