【问题标题】:Outputting special characters correctly (unicode) in PERL在 PERL 中正确输出特殊字符(unicode)
【发布时间】:2014-12-05 15:22:39
【问题描述】:

我正在尝试获取目录中的所有文件名并确定哪些名称包含特殊字符。我正在使用正则表达式

/[^-a-zA-Z0-9_.]/

示例文件(我使用触摸创建):

pdf-2014à014&7_06_64-Os_O&L,_Inc.pdf
pdf-20_06_04-O_OnLine,_Inc.pdf
pdf-20_0_0-Utà_d.wr.pdf
pdf-20_12_28-20.Mga_Grf.Fwd_Notice_KDJFI789&_JFK38.pdf
pdf-2_0_0-C_—_DUKE.pdf
pdf-2_1_3-f_s-M_F_D&A.pdf
pdf_-_2014à014&1007_0617_06264-O_O&L,_Inc.pdf

在我匹配正则表达式中的模式名称之前,Perl 可以输出正确的名称一次。是的 perl 能够以某种方式匹配特殊字符,但是在输出字符时会发生变化。

* pdf-2_0_0-C_—_DUKE.pdf          >        pdf-2_0_0-C_???_DUKE.pdf

我可以尝试取消注释这一行

   #binmode(STDOUT, ":utf8");

然后再次运行命令脚本。确定?标记将被删除,但输出也不同。

* pdf-2_0_0-C_—_DUKE.pdf          >        pdf-2_0_0-C_â_DUKE.pdf

这是我的代码:

use strict;
use warnings;
use File::Find;
use Cwd;

#binmode(STDOUT, ":utf8");

my $starting_directory = cwd();

use Term::ANSIColor;

checkForSpecialChar(cwd());


sub checkForSpecialChar{
    my ($source_dir) = @_;

    chdir $source_dir or die qq(Cannot change into "$source_dir");

    find ( sub {
        return unless -f;   #We want files only
        print "\n";
        while(m/([^-a-zA-Z0-9_.])/g){ 
            chomp($_);
            print "DETECTED: |" . $_ . "|\n";
            print $`;
            print color 'bold red';
            print "$1";
            print color 'reset';
            print  $' . "\n";

        }

    }, ".");

    chdir("$starting_directory");

有什么想法吗?

更新: 嗯,你们是对的,看起来它不是正则表达式的问题。嗨 AKHolland,我尝试将代码更改为与您的测试相同。但仍然会产生与 hypen 和小写字母 a-grave 相同的问题。 而不是一个小写字母a-grave它给了我 a` 当不使用 binmode(STDOUT, ":utf8"); aÌ 当使用 binmode(STDOUT, ":utf8");

use strict;
use warnings;
use File::Find;
use Cwd;
use Encode;
binmode(STDOUT, ":utf8");

my $starting_directory = cwd();

use Term::ANSIColor;

checkForSpecialChar(cwd());


sub checkForSpecialChar{
   my ($source_dir) = @_;

   chdir $source_dir
       or die qq(Cannot change into "$source_dir");

   find ( sub {
      return unless -f;   #We want files only
     print $_ . "\n";
      $_ = Encode::decode_utf8($_);
      for(my $counter =0; $counter < length($_); $counter++) {
        print Encode::encode_utf8(substr($_,$counter,1)) .  "\n";
      } 

}, ".");

chdir("$starting_directory"); }

输出与 binmode(标准输出,“:utf8”); pdf-2_0_0-C_â_DUKE.pdf p d F - 2 _ 0 _ 0 - C _ 一种 _ D ü ķ 乙 . p d F pdf_-_2014aÌ014&1007_0617_06264-O_O&L,_Inc.pdf p d F _ - _ 2 0 1 4 一种 一世 0 1 4 & 1 0 0 7 _ 0 6 1 7 _ 0 6 2 6 4 - ○ _ ○ & 大号 , _ 一世 n C . p d F 无输出 binmode(标准输出,“:utf8”); pdf-2_0_0-C_—_DUKE.pdf p d F - 2 _ 0 _ 0 - C _ — _ D ü ķ 乙 . p d F pdf_-_2014à014&1007_0617_06264-O_O&L,_Inc.pdf p d F _ - _ 2 0 1 4 一种 ̀ 0 1 4 & 1 0 0 7 _ 0 6 1 7 _ 0 6 2 6 4 - ○ _ ○ & 大号 , _ 一世 n C . p d F

【问题讨论】:

  • 你的环境是什么? This version 您的代码在 OSX 输出上正确运行。
  • 嗨米勒,我正在使用 Mac OSX 运行上面的代码。 Bash 能够正确输出它。如果我使用来自 find 的 'print $_',Perl 能够正确打印字符,但是在我将字符串传递给 substr 函数或正则表达式之后,返回字符串已经不同
  • 大家好,我看到了类似的帖子perlmonks.org/bare/?node_id=316862。其中 substr 无法处理 utf8。我认为它与我的问题有一些关系,因为我尝试从它们输出这些文件名,直到我将它们传递给函数。如果我找到解决此问题的方法,我会发布

标签: regex perl unicode character


【解决方案1】:

您需要在输入时对其进行解码,并在输出时对其进行编码。像这样的:

use Encode;
find ( sub {
    $_ = Encode::decode_utf8($_);
    while(m/([^-a-zA-Z0-9_.])/g){
        my $chr = Encode::encode_utf8($1);
        print "$chr\n"
    }
}, ".");

【讨论】:

  • 是的,文件名应被视为二进制数据,因此需要对其进行解码和编码。 Linux 倾向于使用 UTF-8 作为文件名,而 Windows 在其 NTFS 文件系统中使用 UTF-16。 Perl 的Encode 模块为此提供了通用的encodedecode 函数。
【解决方案2】:

pdf-2_0_0-C_—_DUKE.pdf 中的字符 在 utf-8 中用 3 个字符编码:

char Unicode   UTF-8
—    U+2014    \xe2\x80\x94

所以,正如@AKHolland 所说,您必须对其进行编码。

【讨论】:

    【解决方案3】:

    字符“—”是 Em Dash,来自 Unicode 字符集,代码点为 U+2014。

    代码点从 U+0800 到 U+FFFF 的字符使用 UTF-8 中的三个字节进行编码。

    对于 3 字节编码,考虑 16 位二进制版本的代码点 2014h。将十六进制转换为二进制,得到 0010 0000 0001 0100b。

    这是两个字节。我们从哪里得到三个字节?那是因为,UTF-8 编码规则需要:

    • 在 3 字节编码中,“前导”(最高值)字节必须以三个“1”位开始,后跟一个“0”位。因此,我们的前导字节采用 1110 的形式,后跟我们拥有的前 4 位;即 11100010b 或 E2h。
    • 所有“延续”字节(前导字节之后的那些)都以“10”开头。因此,我们的第二个字节变为 10,然后是接下来的 6 位;即 10000000b 或 80h。
    • 同样,第三个字节(即第二个连续字节)变为 10,然后是我们剩余的 6 位;即 10010100b 或 94h。

    您的binmode(STDOUT, ":utf8"); 行仅将输出 将 STDOUT 编码为 UTF-8。但是,正如 cmets 中提到的另一个答案,Windows 文件系统 (NTFS) 使用 UTF-16 作为文件名 - UTF-16 编码使用 两个字节 进行编码上述范围内的字符,而不是三个。这 2 个字节在数字上等于代码点本身,2014h。

    因此,您还需要解码输入。 AKHolland 的回答告诉你怎么做。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-11
      • 2011-03-10
      • 2021-09-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多