我正在尝试以一种无缓冲的方式读取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
更改为sysread
。read
读取,直到它具有所请求的字符数,而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;
}
}