Регулярное выражение, не соответствующее данным и датам

#regex #perl

#регулярное выражение #perl

Вопрос:

У меня есть дамп SQL Select со многими строками, каждая из которых выглядит следующим образом:

 07/11/2011 16:48:08,07/11/2011 16:48:08,'YD','MANUAL',0,1,'text','text','text','text',,,,'text',,,0,0,
  

Я хочу сделать 2 вещи с каждой строкой:

  1. Замените все даты sysdate функцией Oracle. Даты также могут указываться без указания часа (например 07/11/2011 ).
  2. Замените все нулевые значения null строкой

Вот моя попытка:

 $_ =~ s/,(,|n)/,null$1/g;                  # Replace no data by "null"
$_ =~ s/d{2}/d{2}/d{4}.*?,/sysdate,/g;  # Replace dates by "sysdate"
  

Но это преобразовало бы строку в:

 07/11/2011 16:48:08,07/11/2011 16:48:08,'YD','MANUAL',0,1,'text','text','text','text',null,,null,'text',null,,0,0,null
  

хотя я ожидаю, что это будет

 sysdate,sysdate,'YD','MANUAL',0,1,'text','text','text','text',null,null,null,'text',null,null,0,0,null
  

Я не понимаю, почему даты не совпадают и почему некоторые ,, не заменяются на null .

Приветствуется любая информация, заранее спасибо.

Комментарии:

1. Пожалуйста, предоставьте правильный, ожидаемый результат.

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

3. Могут ли эти «текстовые» поля содержать запятые в кавычках?

4. @FailedDev: извините, исправил вопрос. Лукас: верно, похоже, в этом-то и проблема. TLP: нет.

Ответ №1:

d{2}/d{2}/d{4}.*?, не сработало, потому что последнее d не было экранировано.
Если a , может быть с любой стороны или в начале / конце строки, вы могли бы сделать это в 2 шага:

шаг 1
s/(?:^|(?<=,))(?=,|n)/null/g
расширено:

 /
  (?:  ^           # Begining of line, ie: nothing behind us
     | (?<=,)      # Or, a comma behind us
  )
     # we are HERE!, this is the place between characters
  (?=  ,           # A comma in front of us
     | n          # Or, a newline in front of us
  )
/null/g
# The above regex does not consume, it just inserts 'null', leaving the
# same search position (after the insertion, but before the comma).

# If you want to consume a comma, it would be done this way:
s/(?:^|(?<=,))(,|n)/null$1/xg
# Now the search position is after the 'null,'
  

шаг 2
s/(?:^|(?<=,))d{2}/d{2}/d{4}.*?(?=,|n)/sysdate/g

Или вы могли бы объединить их в одно регулярное выражение, используя модификатор eval:
$row =~ s/(?:^|(?<=,))(d{2}/d{2}/d{4}.*?|)(?=,|n)/ length $1 ? 'sysdate' : 'null'/eg;

В разбивке это выглядит так

 s{
   (?: ^ | (?<=,) )  # begin of line or comma behind us
   (                 # Capt group $1
       d{2}/d{2}/d{4}.*?     # date format and optional non-newline chars
     |                          # Or, nothing at all
   )                 # End Capt group 1
  (?= , | n )       # comma or newline in front of us
}{
   length $1 ? 'sysdate' : 'null'
}eg  
  

Если есть вероятность заполнения пробелов без перевода строки, это может быть записано как:

$row =~ s/(?:^|(?<=,))(?:([^Sn]*d{2}/d{2}/d{4}.*?)|[^Sn]*)(?=,|n)/ defined $1 ? 'sysdate' : 'null'/eg;

Комментарии:

1. Большое спасибо! Подстановка даты работает нормально, я просто забыл экранировать последний d. Если вы можете, пожалуйста, объяснить первое регулярное выражение, это было бы потрясающе. Еще раз спасибо!

2. Я предпочитаю иметь отдельные регулярные выражения для удобства чтения. Еще раз спасибо!

3. @m0skit0 — Сообщение изменено, чтобы объяснить регулярное выражение через запятую. Выполнение этого в 2 шага нормально, если регулярные выражения не мешают друг другу, что может быть сделано специально.

Ответ №2:

Вы могли бы сделать это:

 $ cat perlregex.pl
use warnings;
use strict;

my $row = "07/11/2011 16:48:08,07/11/2011 16:48:08,'YD','MANUAL',0,1,'text','text','text','text',,,,'text',,,0,0,n";

print( "$rown" );
while ( $row =~ /,([,n])/ ) { $row =~ s/,([,n])/,null$1/; }
print( "$rown" );
$row =~ s/d{2}/d{2}/d{4}.*?,/sysdate,/g;
print( "$rown" );
  

Что приводит к этому:

 $ ./perlregex.pl
07/11/2011 16:48:08,07/11/2011 16:48:08,'YD','MANUAL',0,1,'text','text','text','text',,,,'text',,,0,0,

07/11/2011 16:48:08,07/11/2011 16:48:08,'YD','MANUAL',0,1,'text','text','text','text',null,null,null,'text',null,null,0,0,null

sysdate,sysdate,'YD','MANUAL',0,1,'text','text','text','text',null,null,null,'text',null,null,0,0,null
  

Это, безусловно, можно было бы оптимизировать, но это передает суть.

Комментарии:

1. Что, если одно из текстовых полей содержит запятые? Например. 'foo,,,bar' .

2. @TLP, хорошая мысль, и если это так, я бы переключился на использование пакета для обработки синтаксического анализа, а затем снова собрал его самостоятельно. В прошлом я использовал Text:: CSV для достижения этой цели. Здесь должно быть достаточно, вы бы просто прочитали в каждой строке, а затем записали новую строку, подставляя значения по мере необходимости.

3. @TLP хороший момент, но текстовые поля, насколько я знаю, не содержат запятых.

4. @Lucas: спасибо, хороший ответ. Я предпочитаю sln, хотя в нем нет цикла.

Ответ №3:

Вы хотите что-то заменить. Обычно для этого лучшим вариантом являются предварительные просмотры :

 $subject =~ s/(?<=,)(?=,|$)/null/g;
  

Объяснение :

 "
(?<=       # Assert that the regex below can be matched, with the match ending at this position (positive lookbehind)
   ,          # Match the character “,” literally
)
(?=        # Assert that the regex below can be matched, starting at this position (positive lookahead)
              # Match either the regular expression below (attempting the next alternative only if this one fails)
      ,          # Match the character “,” literally
   |          # Or match regular expression number 2 below (the entire group fails if this one fails to match)
      $          # Assert position at the end of the string (or before the line break at the end of the string, if any)
)
"
  

Во-вторых, вы хотите заменить даты :

 $subject =~ s!d{2}/d{2}/d{4}.*?(?=,)!sysdate!g;
  

Это почти то же самое с вашим исходным регулярным выражением. Просто замените последнее на lookahead. (Если вы не хотите его заменять, не сопоставляйте его.)

 # d{2}/d{2}/d{4}.*?(?=,)
# 
# Match a single digit 0..9 «d{2}»
#    Exactly 2 times «{2}»
# Match the character “/” literally «/»
# Match a single digit 0..9 «d{2}»
#    Exactly 2 times «{2}»
# Match the character “/” literally «/»
# Match a single digit 0..9 «d{4}»
#    Exactly 4 times «{4}»
# Match any single character that is not a line break character «.*?»
#    Between zero and unlimited times, as few times as possible, expanding as needed (lazy) «*?»
# Assert that the regex below can be matched, starting at this position (positive lookahead) «(?=,)»
#    Match the character “,” literally «,»
  

Комментарии:

1. Спасибо за ответ и объяснение. Почему я должен исправлять второе регулярное выражение, если оно работает? Я просто забыл экранировать последнее d … 😛

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

3. Спасибо, но мой вопрос больше касается «почему мое исходное регулярное выражение не соответствует тому, что мне нужно» 🙂

Ответ №4:

Может быть .*? слишком жаден, попробуй:

 $_ =~ s/d{2}/d{2}/d{4}[^,] ,/sysdate,/g;
  

Комментарии:

1. Проблема заключалась в том, что последняя буква d не была экранирована. Глупая ошибка. Спасибо за ответ 🙂