perl 如何用正则表达式检查输入字符串是否为固定格式

58wvjzkj  于 2022-11-15  发布在  Perl
关注(0)|答案(6)|浏览(158)

我正在写一个程序来存储家庭成员的数据。
输入格式如下,

Country Husband wife child pet

示例性输入

Japan ken Annie may money

输入地区,丈夫,妻子,孩子和宠物的名字,并以空格分隔,我想检查用户输入是否正确。我尝试了

( /^(.+)(\s(.+)){4}$/ ) ? print "good" : print "fail";

但它只能判断是否输入了5个以上的单词,而不能准确判断5个。比如,如果输入

Japan ken Annie may money hank queen

还是会通过审判的。
请告诉我我做错了什么,如何改正?

gab6jxml

gab6jxml1#

使用一个好的库来读取输入。† Getopt::Long是优秀的,实际上是一个标准

use warnings;
use strict;
use feature 'say';

use Getopt::Long;
use List::Util qw(any);

my ($country, $husband, $wife, $child, $pet);

GetOptions( 
    'country=s'   => \$country,  
    'husband=s'   => \$husband, 
    'wife=s'      => \$wife, 
    'child|kid=s' => \$child,
    'pet=s'       => \$pet
);

# If they all must be submitted, and no other input, check
usage() if any { not defined $_ } $country, $husband, $wife, $child, $pet;
usage() if @ARGV;

say "Family of $wife and $husband come from $country";

sub usage {
    say STDERR "
Usage: $0 ...

All listed arguments are mandatory.

No other input is supported.
";

    exit;
}

一个选项值有多个单词在引号中。我上面展示了如何为输入选项设置替代名称,它们可以被缩短,只要没有歧义,一个连字符可以被丢弃,等等。

program.pl --husband Ken -w "Jo Ann" -kid May

有关库使用中更多功能,请参阅文档
在命令行上提交给Perl程序的选项被放置在@ARGVpredefined variable中。(那些不以-开头也不直接跟在-后面的字符串)。这允许我们传递其他输入,通常是文件名,然后我们可以直接从@ARGV中使用它们。(否则它们将被忽略。)
因此,如果您希望抑制任何其他输入,请检查在库完成解析之后,@ARGV中是否没有任何内容。
我使用List::Util来避免检查所有变量,但是如果你想向用户返回错误输入的特定消息,请这样做。
由于问题中的大小写不一致,我将所有选项都设为小写。请根据需要进行调整。
[2]手工解析输入给程序员带来了很大的负担。
我们需要设计一个看起来适合我们的目的的系统(可能很难提前告诉)--如何从命令行中分离选项?多个单词、特殊字符等情况如何?这是哪个shell的?顺序--位置?(容易出错,很难检查!)等等。
然后,我们需要解析它,预测并捕捉各种可能的错误,可能会引入一些后处理。
大量的工作,调试和测试,迭代...结果很可能是脆弱的,所以当未来有一个改变,其余的(或所有的?)可能需要返工。
这就是图书馆的作用所有这些,甚至更多,都已完成。

bwitn5fc

bwitn5fc2#

在字符串Japan ken Annie may money hank queen上,第一个(.+)Japan ken Annie匹配,因此正则表达式的其余部分能够毫无问题地匹配四个额外的名称。
问题是点.也匹配空格。
对于由空格(或任何其他分隔符)分隔的单词,常用的解决方案是使用以下表达式:

^ something (?: separator something )quantifier $ # Note: don't take into account spaces

 (where 'something' cannot contain the separator)

所以在你的例子中,你可以写:

^\S+(?:\s+\S+){4}$

其中\S+表示:任何非空白字符,1次或多次
请注意,\s匹配任何空白字符(包括新行)因此,如果您正在阅读整个文件(而不是逐行),建议使用\h代替(匹配 horizontal 空白字符)

^\S+(?:\h+\S+){4}$

如果您使用\s并且不逐行处理内容,则正则表达式可能会尝试跨多行匹配数据,这对于您的情况是错误的。
此外,如果要阅读整个文件,则可能还需要使用m修饰符

/^\S+(?:\h+\S+){4}$/m

(?m)^\S+(?:\h+\S+){4}$

因此^$匹配行的开始和结束(而不是字符串的开始和结束)
如果不打算捕获数据,请考虑使用非捕获组(?:)
如果您计划捕获该行的所有数据,则可以使用以下正则表达式:

^(\S+)\h+(\S+)\h+(\S+)\h+(\S+)\h+(\S+)$
ipakzgxi

ipakzgxi3#

使用简单的正则表达式很少能在一个步骤中完成输入数据验证。
请检查下面的演示代码,看是否有可能国家/名称中包含空格和破折号,这是您的正则表达式所建议的,将无法正确处理。
为了避免潜在的缺陷,不要使用空格作为字段分隔符--名称和国家/地区可能会包含空格/破折号--使用,填充更自然。

use strict;
use warnings;
use feature 'say';

use Data::Dumper;

my $data;
my @header = split(/,/, <DATA>);

chomp @header;

while(my $line = <DATA>) {
    chomp $line;
    my @read = split(/,/,$line);
    say "Warning: $line number of arguments is " . scalar @read
        unless @read == 5;
    $data->@{@header} = @read;
    $data->{$_} =~ /[^a-z -]+/i && say "Warning: '$_ => $data->{$_}' does not look right"
        for @header;
    say Dumper($data);
}

__DATA__
Country,Husband,wife,child,pet
Japan,ken,Annie,may,money
China,Sonny,Ae-Cha,Bora,coin,hummer
South Korea,Sonny2,Ae-Cha,Bora,coin

输出样本

$VAR1 = {
          'Husband' => 'ken',
          'pet' => 'money',
          'child' => 'may',
          'wife' => 'Annie',
          'Country' => 'Japan'
        };

Warning: China,Sonny,Ae-Cha,Bora,coin,hummer number of arguments is 6
$VAR1 = {
          'Husband' => 'Sonny',
          'pet' => 'coin',
          'child' => 'Bora',
          'wife' => 'Ae-Cha',
          'Country' => 'China'
        };

Warning: 'Husband => Sonny2' does not look right
$VAR1 = {
          'Husband' => 'Sonny2',
          'pet' => 'coin',
          'child' => 'Bora',
          'wife' => 'Ae-Cha',
          'Country' => 'South Korea'
        };
f45qwnt8

f45qwnt84#

请注意,如果您有一个可能包含空格的类别,以空格分割字串并不是一个好的方法,例如his answer中的“What if the country is South Korea?”Polar Bear建议使用逗号作为分隔符号,这将允许使用South Korea。其他的解决方法可能包括在引号中加上空格,以及使用可以行程引号的模块,例如Text::ParseWords。这是Perl中一个核心模块。
使用Text::ParseWords
第一个
但是我认为最合适的方法是在空格上拆分字符串并计算得到的字段,你可以像上面那样用quotewords来计算,然后插入一个测试,例如:

if (@data == 5) {
    print "Correct number of args";
} elsif (@data < 5) {
    print "Too few args";
} # etc.....

您也可以手动分割字串:

my @data = split ' ', $str;

使用正则表达式进行计数的一种简单方法是匹配您想要匹配的内容,然后将其分配给一个标量上下文,使用一点Perl技巧:

my $count = () = $str =~ /\S+/g;  # how many non-whitespace matches do we get?

赋值中的空列表()将把正则表达式放入列表上下文,并返回左侧标量的匹配数。
但我觉得使用单个字符串数据输入并不是最好的方法,如果你有确切数量的输入要获取,为什么不单独获取呢?

use strict;
use warnings;
use Data::Dumper;
use feature 'say';

my %data;
my @inputs = qw(country husband wife child pet);

for my $input (@inputs) {
    print "Enter value for '$input': ";
    chomp($data{$input} = <>);
}
print Dumper \%data;

输出量:

Enter value for 'country': Sweden
Enter value for 'husband': Bob
Enter value for 'wife': Barbie
Enter value for 'child': Baby
Enter value for 'pet': Fido
$VAR1 = {
          'child' => 'Baby',
          'country' => 'Sweden',
          'husband' => 'Bob',
          'pet' => 'Fido',
          'wife' => 'Barbie'
        };
jtjikinw

jtjikinw5#

问题出在.上,它与单词之间的空格相匹配。用答案的组成部分(例如字母、数字和破折号)来替换它会更好。
也许有更好的解决办法,但这是我想到的。

/^([\w\d-]+\s[\w\d-]+){4}$/
hgc7kmma

hgc7kmma6#

在开始时第一个.+匹配整个字符串Japan ken Annie may money hank queen,然后给予4个字符串,每个字符串匹配代码\s(.+),这是错误的。因此,代码的结果是:
第1组Japan ken Annie
第2组
您可以使用\w+而不是.+来修复它。
下面是一个演示:https://regex101.com/r/jQ8IE0/1

相关问题