如何在Perl中读取未缓冲的UTF-8



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

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

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

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

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

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

首先,您需要将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 ...
   }
}

在utf-8模式中,读取会对部分字符进行重试。不过,这会破坏您对read-on-:unix的特殊使用。我想这是一个"不要这样做"的例子。

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

这似乎有效,尽管你几乎肯定会想进入睡眠状态(也许是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 "$in";
    }
    elsif (defined $i) {
        last;
    }
}

最新更新