Анализ атрибутов с помощью регулярных выражений в Perl
Вопрос
Вот проблема, с которой я недавно столкнулся.У меня есть строки атрибутов формы
"x=1 and y=abc and z=c4g and ..."
Некоторые атрибуты имеют числовые значения, некоторые — буквенные значения, некоторые — смешанные, некоторые — даты и т. д.
Каждая строка предполагаемый иметь "x=someval and 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
атрибуты.Это действительная строка.
Я опубликую свое решение в качестве ответа, но оно не соответствует моим предпочтениям в отношении одного регулярного выражения.
Решение
Я не лучший специалист по регулярным выражениям, но это кажется очень близким к тому, что вы ищете:
/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.
Это, конечно, исключает множество проверок ошибок, и я не знаю всего о ваших входных данных, но, похоже, это работает.
Другие советы
Предполагая, что вы также хотите что-то сделать с другими парами имя=значение, я бы сделал это следующим образом (используя Perl версии 5.10):
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 и $3 (?:делает ее группой без захвата) и гарантирует, что строка начинается с "x=", а не совпадет с "not_x="
Если вы лучше знаете, какими будут значения x и y, это следует использовать для дальнейшего ужесточения регулярного выражения:
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 имел то же ограничение, строка не удалась бы.
Радд и Себджайр помогли вам большую часть пути, но у них обоих есть определенные проблемы:
Радд предложил:
/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;
Я пропустил некоторые дополнительные проверки и обработку ошибок.Этот метод работает, но он не такой лаконичный и красивый, как мне хотелось бы.Я надеюсь, что кто-нибудь предложит мне лучшее предложение.