我在一个脚本中走得很远,我正在努力发现它在读取UTF-8字符时遇到了问题.
我在瑞典有一个联系人在他的机器上创建了一个虚拟机,其中有一些UTF-8,当我的脚本击中该虚拟机时,它失去了理智,但它能够读取所有其他处于"正常"状态的虚拟机字符集.
无论如何,也许我的代码会更有意义.
#!/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";
}
Run Code Online (Sandbox Code Playgroud)
我把它缩小到我的正则表达式作为问题,因为这里是应用正则表达式之前服务器的原始输出.
416
TEST Box åäö!"''*#
Run Code Online (Sandbox Code Playgroud)
这是我应用正则表达式后得到的结果
416
TEST
Run Code Online (Sandbox Code Playgroud)
由于某种原因,正则表达式不匹配,我只是不知道为什么.并且示例中的当前正则表达式是使其工作的第三次尝试.
我匹配的FULL行看起来像这样.我的正则表达式的完成方式是因为我只需要前两个信息块,你想要复制整行的表达式.
代码:
432 TEST Box åäö!"''*# [Store] TEST Box +w6XDpMO2IQ-_''_+Iw/TEST Box +w6XDpMO2IQ _''_+Iw.vmx slesGuest vmx-04
Run Code Online (Sandbox Code Playgroud)
子模式
(?<name> .+?)\s+
Run Code Online (Sandbox Code Playgroud)
在你的正则表达式中意味着"匹配并记住一个或多个非换行符,但一旦找到空格就停止",因此$name包含TEST因为模式在看到之前的空格时停止匹配Box.
该VI工具箱维基给出了一个例子的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
Run Code Online (Sandbox Code Playgroud)
非贪婪量词 +?意味着匹配一个或多个东西,但匹配想要尽快将控制权交给模式的其余部分.请记住,[它在正则表达式中具有特殊含义,但模式\[匹配文字而不是引入字符类.
我认为这种技术是书挡或伸展和伸展.如果要提取难以表征的大量文本,请查找易于匹配的周围特征 - 通常简单^或者$.然后使用弹性图案来抓住两者之间的所有东西,通常(.+)或(.+?).阅读perlre文档的"Quantifiers"部分,了解您的许多选项.
这解决了眼前的问题,您还可以在一些方面添加润色.
不要无条件地使用$1,$2和朋友!在使用捕获变量之前,始终测试模式是否匹配.例如
if (/(foo|bar|baz)/) {
print "got $1\n";
}
else {
print "no match\n";
}
Run Code Online (Sandbox Code Playgroud)
不受保护的print $1可以产生难以调试的令人惊讶的结果.
明智地使用Perl的默认值可以帮助强调计算并让机制淡入背景.滴加$vm赞成$_作为隐含的循环变量和隐式匹配目标使得一个更好的结果.
您的意见仅仅是从Perl翻译成英语.最有帮助的评论解释了为什么,而不是什么.另请记住Rob Pike 关于评论的建议:
如果您的代码需要理解注释,那么最好重写它以便更容易理解.
在赋值中%+,引号不起作用.值已经是字符串,因此请删除引号.
my $id = $+{id};
my $name = $+{name};
Run Code Online (Sandbox Code Playgroud)
下面是您的代码的修改版本,它捕获数字之后但[store]进入之前的所有内容$name.该UTF8编译声明你的源代码 -不是,与一个常见的错误,你的输入中包含UTF-8.下面的测试用一个罐装模拟瑞典VM上的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";
}
}
Run Code Online (Sandbox Code Playgroud)
输出:
416 TEST Box åäö!"''*#