Вот с какой проблемой я недавно столкнулся. У меня есть строки атрибутов вида
"x=1 and y=abc and z=c4g and ..."
Некоторые атрибуты имеют числовые значения, некоторые - альфа-значения, некоторые - смешанные, некоторые - даты и т.д.
Каждая строка полагается иметь "x=someval и y=anotherval
" в начале, но некоторые не имеют этого. Мне нужно сделать три вещи.
x
и y
. x
и y
.С учетом примера, приведенного вверху, это приведет к следующим переменным:
$x = 1;
$y = "abc";
$remainder = "z=c4g and ..."
Мой вопрос: есть ли (достаточно) простой способ разобрать эти переменные и проверить их с помощью одного регулярного выражения? Т.е:
if ($str =~ /someexpression/)
{
$x = $1;
$y = $2;
$remainder = $3;
}
Обратите внимание, что строка может состоять только из атрибутов x
и y
. Это допустимая строка.
Я опубликую свое решение в качестве ответа, но оно не соответствует моим предпочтениям в отношении единственного регекса.
Принятие Вас также хочет сделать что-то с другими name=value парами, которые это - то, как я сделал бы это (использование версии 5.10 Perl):
use 5.10.0;
use strict;
use warnings;
my %hash;
while(
$string =~ m{
(?: ^ | \G ) # start of string or previous match
\s*
(?<key> \w+ ) # word characters
=
(?<value> \S+ ) # non spaces
\s* # get to the start of the next match
(?: and )?
}xgi
){
$hash{$+{key}} = $+{value};
}
# to make sure that x & y exist
die unless exists $hash{x} and exists $hash{y};
На более старом Perls (по крайней мере, Perl 5.6);
use strict;
use warnings;
my %hash;
while(
$string =~ m{
(?: ^ | \G ) # start of string or previous match
\s*
( \w+ ) = ( \S+ )
\s* # get to the start of the next match
(?: and )?
}xgi
){
$hash{$1} = $2;
}
# to make sure that x & y exist
die unless exists $hash{x} and exists $hash{y};
Они обладают дополнительным преимуществом продолжения работать, если Вы должны работать с большим количеством данных.
Я не очень хорошо разбираюсь в регулярных выражениях, но, похоже, это довольно близко к тому, что вы ищете:
/x=(.+) and y=([^ ]+)( and (.*))?/
За исключением того, что вы используете $1, $2 и $4. Используется:
my @strs = ("x=1 and y=abc and z=c4g and w=v4l",
"x=yes and y=no",
"z=nox and w=noy");
foreach (@strs) {
if ($_ =~ /x=(.+) and y=([^ ]+)( and (.*))?/) {
$x = $1;
$y = $2;
$remainder = $4;
print "x: $x; y: $y; remainder: $remainder\n";
} else {
print "Failed.\n";
}
}
Вывод:
x: 1; y: abc; remainder: z=c4g and w=v4l
x: yes; y: no; remainder:
Failed.
Это, конечно, оставляет много проверки ошибок, и я не знаю всего о ваших входных данных, но это, кажется, работает.
В качестве довольно простой модификации к версии Радда,
/^x=(.+) and y=([^ ]+)(?: and (.*))?/
позволит вам использовать $1, $2 и $3 (символ ?: делает его не захватывающей группой), и будет гарантировать, что строка начинается с "x=", вместо того, чтобы позволить "not_x=" соответствовать
Если вы лучше знаете, какими будут значения x и y, это следует использовать для дальнейшего ужесточения regex:
my @strs = ("x=1 and y=abc and z=c4g and w=v4l",
"x=yes and y=no",
"z=nox and w=noy",
"not-x=nox and y=present",
"x=yes and w='there is no and y=something arg here'");
foreach (@strs) {
if ($_ =~ /^x=(.+) and y=([^ ]+)(?: and (.*))?/) {
$x = $1;
$y = $2;
$remainder = $3;
print "x: {$x}; y: {$y}; remainder: {$remainder}\n";
} else {
print "$_ Failed.\n";
}
}
Выход:
x: {1}; y: {abc}; remainder: {z=c4g and w=v4l}
x: {yes}; y: {no}; remainder: {}
z=nox and w=noy Failed.
not-x=nox and y=present Failed.
x: {yes and w='there is no}; y: {something}; remainder: {}
Обратите внимание, что отсутствие части последнего теста связано с тем, что текущая версия теста y не требует пробелов, если бы тест x имел такое же ограничение, эта строка не прошла бы.
Rudd и Cebjyre получили Вас большая часть пути там, но у них обоих есть определенные проблемы:
Красноперка предложила:
/x = (. +) и y = ([^] +) (и (.*))? /
Cebjyre изменил его к:
/^x = (. +) и y = ([^] +) (?: и (.*))? /
Вторая версия лучше, потому что она не перепутает " not_x=foo" с " x=foo" но примет вещи, такие как " x=foo z=bar y=baz" и набор 1$ = " foo z=bar" который является нежелательным.
Это, вероятно, что Вы ищете:
/^x = (\w +) и y = (\w +) (?: и (.*))? /
Это отвергает что-либо между x = и y = варианты, помещает и позволяет и дополнительный " и..." который будет в 3$
Вот, в принципе, что я сделал, чтобы решить эту проблему:
($x_str, $y_str, $remainder) = split(/ and /, $str, 3);
if ($x_str !~ /x=(.*)/)
{
# error
}
$x = $1;
if ($y_str !~ /y=(.*)/)
{
# error
}
$y = $1;
Я'опустил некоторую дополнительную валидацию и обработку ошибок. Эта техника работает, но она не такая лаконичная и красивая, как мне бы хотелось. Я надеюсь, что кто-нибудь предложит мне лучший вариант.