我在一个正在处理的脚本中走得很远但发现它在读取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
答案 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发行版并重新使用它,但在野外,你可能正在处理其他一些编码。
您可以跳过对decode
和encode
的调用,因为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');