Программисту понадобилось быстренько отфильтровать и переформатировать один лог. Исходный файл содержит по одной json-записи в строке, в итоге должен получиться tab separated файл из двух колонок.
Источник данных ненадежный, в прошлый раз в логах попадались буквы вместо цифр, поэтому программист подстраховался eval
-ом и валидацией.
Получился такой скрипт:
#!/usr/bin/perl
use strict;
use warnings;
use JSON;
while(<>){
my $data = eval{decode_json($_)};
next unless $data;
next unless is_interesting_id($data->{id}) && is_valid_int($data->{sum});
print join("\t", $data->{id}, $data->{sum});
print "\n";
}
sub is_interesting_id
{
return $_[0] =~ /^5665[0-9]{7,13}$/ ? 1 : 0;
}
sub is_valid_int
{
return $_[0] =~ /^[0-9]+$/ ? 1 : 0;
}
Все хорошо? Нет! На выходе некоторые строчки пустые, а некоторые содержат только одно значение вместо положенных двух.
Пример подозрительного результата:
566512345671 123
566512345672 123
566512345673
123
566512345674 123
Но как? Ведь там же валидация…
А вот пример подозрительных данных:
{"id":"566512345671", "sum": 123}
{"id":"566512345672", "sum": "123\n"}
{"id":"566512345673\n", "sum": "123"}
{"id":"566512345674", "sum": "123"}
{"id":"5665123
Но как эти данные проходят проверку?
Проблема в том, что доллар $
в регулярных выражениях матчится либо в конце строки,
либо перед символом \n
в конце строки.
К примеру, строка "123\n"
успешно сопоставляется с регулярным выражением /^[0-9]+$/
.
Чтобы в регулярном выражении поймать настоящий конец строки, стоит использовать \z
:
sub is_interesting_id
{
return $_[0] =~ /^5665[0-9]{7,13}\z/ ? 1 : 0;
}
Также см. perldoc perlre (раздел Assertions).