如何更改正则表达式以读取UTF-8?

时间:2011-02-08 13:37:45

标签: regex perl utf-8

我在一个正在处理的脚本中走得很远但发现它在读取UTF-8个字符时遇到了问题。

我在瑞典有一个联系人,他的机器上有一个VM,其中包含一些UTF-8,当我的脚本命中VM时,它失去了理智,但它能够读取所有其他虚拟机这是“正常”的字符集。

无论如何,也许我的代码会更有意义。

#!/usr/bin/perl
use strict;
use warnings;
#use utf8;
use Net::OpenSSH;

# Create a hash for storing the options needed by Net::OpenSSH
my %ssh_options = (
    port => '22',
    user => 'root',
    password => 'password'
);

# Create a new Net::OpenSSH object
my $ssh = Net::OpenSSH->new('192.168.2.101', %ssh_options);

# Create an array and capture the ESX\ESXi output from the current server
my @getallvms = $ssh->capture('vim-cmd vmsvc/getallvms');
shift @getallvms;
# Process data gathered from server
foreach my $vm (@getallvms) {
    # Match ID, NAME
    $vm =~  m/^(?<id> \d+)\s+(?<name> .+?)\s+/xm;
    my $id = "$+{id}";
    my $name = "$+{name}";
    print "$id\n";
    print "$name\n";
    print "\n";
}

我已将其缩小到我的正则表达式作为问题,因为这里是应用正则表达式之前服务器的原始输出。

416
TEST Box åäö!"''*#

这是我应用正则表达式后得到的结果

416
TEST

由于某种原因,正则表达式不匹配,我只是不知道为什么。并且示例中的当前正则表达式是使其工作的第三次尝试。

我匹配的FULL行看起来像这样。我的正则表达式的完成方式是因为我只需要前两个信息块,你想要复制整行的表达式。

代码:

432    TEST Box åäö!"''*#   [Store] TEST Box +w6XDpMO2IQ-_''_+Iw/TEST Box +w6XDpMO2IQ _''_+Iw.vmx   slesGuest    vmx-04

4 个答案:

答案 0 :(得分:7)

子模式

(?<name> .+?)\s+
正则表达式中的

表示“匹配并记住一个或多个非换行符,但只要找到空格就会停止”,因此$name包含TEST因为模式在看到时停止匹配Box之前的空间。

VI Toolkit wiki给出了example getallvms子命令的输出:

# vmware-vim-cmd -H 10.10.10.10 -U root -P password /vmsvc/getallvms
Vmid    Name               File                 Guest OS       Version   Annotation
64     bartPE    [store] BartPE/BartPE.vmx     winXPProGuest     vmx-04
96     trustix   [store] Trustix/Trustix.vmx   otherLinuxGuest   vmx-04

此案例与您的问题中的示例略有不同,但似乎我们可以将[store]作为匹配的保险杠:

/^(?<id> \d+) \s+ (?<name> .+?) \s+ \[store]/mix

非贪婪的quantifier +?意味着匹配一个或多个东西,但匹配希望尽快将控制权交给模式的其余部分。请记住,[在正则表达式中具有特殊含义,但模式\[与文字匹配,而不是引入字符类。

我认为这种技术是书挡或伸展和伸展。如果要提取难以表征的大量文本,请查找易于匹配的周围要素 - 通常只需^$。然后使用弹性模式抓取其间的所有内容,通常为(.+)(.+?)。请阅读“Quantifiers” section of the perlre documentation以获取有关众多选项的说明。

这可以解决当前的问题,您还可以在一些方面添加润色。

请勿无条件地使用$1$2和朋友!在使用捕获变量之前,始终测试模式是否匹配。例如

if (/(foo|bar|baz)/) {
  print "got $1\n";
}
else {
  print "no match\n";
}

未受保护的print $1会产生难以调试的令人惊讶的结果。

明智地使用Perl的默认值可以帮助强调计算并让机制淡入背景。删除$vm以支持$_作为隐式循环变量和隐式匹配目标可以获得更好的结果。

您的评论仅仅是从Perl翻译成英语。最有帮助的评论解释了为什么,而不是什么。另请记住Rob Pike的advice on commenting

  

如果您的代码需要理解评论,最好重写它以便更容易理解。

%+的作业中,引号没有做任何有用的事情。值已经是字符串,因此请删除引号。

my $id   = $+{id};
my $name = $+{name};

以下是代码的修改版本,可以在数字之后但[store]之前捕获$name之前的所有内容。 utf8 pragma声明您的源代码 -not,与常见错误一样,您的输入包含UTF-8。下面的测试模拟了瑞典虚拟机上echo的预留vim-cmd

正如Tom建议的那样,我使用Encode模块解码通过SSH连接到达的输出,并在打印之前对其进行编码以使本地主机受益。

perlunifaq文档建议将外部数据解码为Perl的内部格式,然后在编写之前对任何输出进行编码。我假设从$ssh->capture(...)返回的值使用UTF-8编码,即远程主机发送UTF-8。我们看到了预期的结果,因为我正在运行一个现代的Linux发行版并重新使用它,但在野外,你可能正在处理其他一些编码。

您可以跳过对decodeencode的调用,因为Perl的内部格式恰好与您正在使用的主机格式相匹配。但总的来说,偷工减料会让你陷入困境:

最后,代码!

#! /usr/bin/env perl

use strict;
use utf8;
use warnings;

use Encode;
use Net::OpenSSH;

my %ssh_options = ();
my $ssh = Net::OpenSSH->new('localhost', %ssh_options);

# Create an array and capture the ESX\ESXi output from the current server
#my @getallvms = $ssh->capture('vim-cmd vmsvc/getallvms');
my @getallvms = $ssh->capture(<<EOEcho);
echo -e 'JUNK\n416 TEST Box åäö!"'\\'\\''*#    [Store] TEST Box +w6XDpMO2IQ-_''_+Iw/TEST Box +w6XDpMO2IQ _''_+Iw.vmx   slesGuest    vmx-04'
EOEcho
shift @getallvms;

for (@getallvms) {
  $_ = decode "utf8", $_, Encode::FB_CROAK;

  if (/^(?<id> \d+) \s+ (?<name> .+?) \s+ \[store]/mix) {
    my $id   = $+{id};
    my $name = $+{name};
    print encode("utf8", $id),   "\n",
          encode("utf8", $name), "\n",
          "\n";
  }
  else {
    print "no match\n";
  }
}

输出:

416
TEST Box åäö!"''*#

答案 1 :(得分:4)

如果您知道您使用的字符串是UTF-8而Net :: OpenSSH没有(因此不会将其标记为),您可以将其转换为内部表示形式Perl可以使用其中一个:

use Encode;
decode_utf8( $in_place );
$decoded = decode_utf8( $raw );

答案 2 :(得分:3)

所以你确定,Perl将这些名称理解为UTF-8编码的字符串。到目前为止,我认为它没有。全面概述about UTF-8 in Perl

您可以使用Encode::is_utf8测试字符串unicodeness,并使用Encode::decode('UTF-8', $your_string)对其进行解码。

在Perl,恕我直言,UTF-8相当混乱。你必须非常耐心。

要以漂亮的方式打印UTF-8字符串,您应该在脚本中使用类似的东西:

BEGIN {
   binmode(STDOUT, ':encoding(UTF-8)');
   binmode(STDERR, ':encoding(UTF-8)');  # Error messages
}

如果你让Perl了解你的UTF-8名称,你也可以正确地使用它们。

答案 3 :(得分:3)

Recent Net::OpenSSH releases本身支持捕获方法中的字符集编码/解码:

my @getallvms = $ssh->capture({stream_encoding => 'utf8'},
                              'vim-cmd vmsvc/getallvms');