我正在开发一个应用程序,它在命令行中使用正则表达式作为用户输入,然后应用该正则表达式查找当前目录下的某些文件。该应用程序支持UTF-8输入,并且应该能够找到UTF-8编码的文件名。这是一个例子:
use feature qw(say);
use open qw( :std :utf8 );
use strict;
use utf8;
use warnings;
use Encode ();
use File::Find::Rule;
system 'touch', 'aæ', 'bæ', 'aa'; # some test files,
my $pat = 'æ$';
my $pat_encode = encode( $pat );
run_test( $pat_encode, 'With encode()' );
run_test( $pat, 'Without encode()' );
my $pat2 = '[æ]$';
my $pat2_encode = encode( $pat2 );
run_test( $pat2_encode, 'With encode()' );
sub encode {
return Encode::encode('UTF-8', $_[0], Encode::FB_CROAK | Encode::LEAVE_SRC);
}
sub run_test {
my ( $pat_encode, $test_str ) = @_;
say $test_str;
say '-' x length $test_str;
say "";
my @files = File::Find::Rule->new->name( qr/$pat_encode/ )->in('.');
for (@files) {
$_ = Encode::decode('UTF-8', $_, Encode::FB_CROAK | Encode::LEAVE_SRC );
}
say $_ for @files;
}
输出结果为:
With encode()
-------------
aæ
bæ
Without encode()
----------------
With encode()
-------------
aæ
bæ
我希望最后的正则表达式[æ]$
在编码后不起作用,因为æ
将扩展为两个字节0xC3A6
,但不知何故,似乎Perl知道正则表达式以UTF-8编码,并使其有效。
我想知道是否有人知道为什么后一个例子正在工作,如果还有其他情况下编码正则表达式不起作用? (所以我想确定是否可以使用File::Find::Rule
或者是否应该切换到File::Find
,这样我就可以避免编写正则表达式。)
答案 0 :(得分:3)
事实证明,对正则表达式进行编码是不安全的。特别是如果括号表达式后跟一个或多个字符,则正则表达式可能会选择不需要的文件。原因是UTF-8编码版本中只有一个字节将与括号表达式匹配。考虑我的脚本的以下修改:
system 'touch', 'aæ', 'aæ1', 'aa'; # some test files,
my $pat = 'æ.$';
my $pat_encode = encode( $pat );
run_test( $pat_encode, 'With encode()' );
run_test( $pat, 'Without encode()' );
my $pat2 = '[æ].$';
my $pat2_encode = encode( $pat2 );
run_test( $pat2_encode, 'With encode()' );
现在这应该只返回文件aæ1
,但$pat2
正则表达式也将返回aæ
,因为只有编码æ
的两个字节中的第一个将是括号表达式使用,最后一个字节与.
中的$pat2
尾随匹配。
输出结果为:
With encode()
-------------
aæ1
Without encode()
----------------
With encode()
-------------
aæ
aæ1
解决方案似乎是使用File::Find
代替:
use File::Find ();
system 'touch', 'aæ', 'aæ1', 'aa'; # some test files,
my $pat = '[æ].$';
my $files = find_files( $pat );
say $_ for @$files;
sub decode {
return Encode::decode('UTF-8', $_[0], Encode::FB_CROAK | Encode::LEAVE_SRC );
}
sub find_files {
my ( $pat ) = @_;
my @files;
File::Find::find( sub { wanted( $pat, \@files ) }, '.' );
return \@files;
}
sub wanted {
my ( $pat, $files ) = @_;
my $name = decode( $_ );
my $full_name = decode( $File::Find::name );
push @$files, $full_name if $name =~ /$pat/;
}
现在输出正确:
./aæ1
<强>更新强>:
事实上,毕竟可以使用File::Find::Rule
。只需使用exec
规则代替name
规则:
my $pat = '[æ].$';
my $files = find_files( $pat );
say for @$files;
sub find_files {
my ( $pat ) = @_;
my @files = File::Find::Rule->new->exec( sub { wanted( $pat ) } )->in('.');
for (@files) {
$_ = decode( $_ );
}
return \@files;
}
sub wanted {
my ( $pat ) = @_;
my $name = decode( $_ );
return ( $name =~ /$pat/ ) ? 1 : 0;
}
输出现在是:
aæ1