#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 вещи с каждой строкой:
- Замените все даты
sysdate
функцией Oracle. Даты также могут указываться без указания часа (например07/11/2011
). - Замените все нулевые значения
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 не была экранирована. Глупая ошибка. Спасибо за ответ 🙂