Анализ атрибутов с помощью регулярного выражения в Perl

Вот проблема, с которой я столкнулся недавно. У меня есть строки атрибутов вида

"x=1 and y=abc and z=c4g and ..."

Некоторые атрибуты имеют числовые значения, некоторые - альфа-значения, некоторые - смешанные, некоторые - даты и т. Д.

Предполагается, что каждая строка имеет " x=someval and y=anotherval " в начале, но в некоторых нет. Мне нужно сделать три дела.

  1. Проверьте строки, чтобы убедиться, что в них есть xи y.
  2. Фактически проанализируйте значения для xи y.
  3. Достаньте оставшуюся часть строки.

Учитывая пример вверху, это приведет к следующим переменным:

$x = 1;
$y = "abc";
$remainder = "z=c4g and ..."

Мой вопрос: есть ли (разумно) простой способ проанализировать их и проверить с помощью одного регулярного выражения? то есть:

if ($str =~ /someexpression/)
{
    $x = $1;
    $y = $2;
    $remainder = $3;
}

Обратите внимание , что строка может состоять из только x и y атрибутов. Это допустимая строка.

Я отправлю свое решение в качестве ответа, но оно не соответствует моим предпочтениям с одним регулярным выражением.

Ответов (5)

Решение

Я не лучший специалист в области регулярных выражений, но это кажется довольно близким к тому, что вы ищете:

/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=(.+) и 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;

Я пропустил дополнительную проверку и обработку ошибок. Этот метод работает, но он не такой лаконичный и красивый, как мне бы хотелось. Я надеюсь, что у кого-нибудь есть для меня предложение получше.

Как довольно простая модификация версии Радда,

/^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-тест имел такое же ограничение, что и строка не прошла бы.

Предполагая, что вы также хотите что-то сделать с другими парами имя = значение, я бы сделал это следующим образом (используя 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};

У них есть дополнительное преимущество в виде продолжения работы, если вам нужно работать с большим количеством данных.