| Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате |
| Форум программистов > Perl: Общие вопросы > переписать скрипт shell на perl | опять регулярка |
| Автор: ramus 22.4.2008, 23:00 | ||||||
| Есть скрипт на shell который читает конфигурационный файл со строками вида MV /path/to/SRC/dir /path/to/DEST/dir select_CU и в данном случае переносит файлы удовлетворяющие условию выборки select_CU из директории /path/to/SRC/dir в директорию /path/to/DEST/dir правила описаны в файле правил: (для данного примера)
этот shell запускается из cron раз в 5 минут. Проблема в том, что когда записей в конфигурационном файле стало более 1000, то время работы скрипта составляет более минуты. Естественно я взялся переписать все это на перл. И тут возник идеологический вопрос: Нужно описать правило отбора файлов в директории (думаю без регулярки не обойтись). Надо наиболее универсально, но чтобы читаемо и просто. Если условие ИЛИ регулярным выражением описывается легко, то условие И не понятно как. например для приведенного выше примера (отобрать все файлы начинающиеся на С или U или с или u)
(?i) - ignore case но как написать что при этом НЕ заканчивающиеся на *.in_progress ? Ограничение: в основном коде будет что то типа if $_ =~ m/${select_CU}/ и поэтому все условие должно содержаться в select_CU Либо так сделать нельзя и что тогда делать? какие варианты? либо ткните в доку, но чесслово не нашел как :( к сожалению в реальной жизни все сложнее, есть например такие
как такое переписать (если не знакома команда grep, то отобрать все файлы игнорируя регистр начинающиеся на R или E или G или W у которых начиная со второй буквы в имени файла будет 001000 или 100 или 101 или 103 ... И не заканчивающиеся на .in_progress |
| Автор: tolkien 23.4.2008, 03:21 |
| Если вы хотите переписать этот код на PERL и вместо grep использовать регулярные выражения PERL. Тогда вам надо будет поменять формат записи условий в select_CU писать по другому. select_CU="grep -ie ^C -e ^U | grep -iv '[.]in_progress$'" здесь выполняються последовательно два grep grep -ie ^C -e ^U grep -iv '[.]in_progress$'" на PERL /^[cCuU]/ /\.n_progress$/ надо придумать свой разделитель между разными рег. выраж. Например такой ### select_CU=^[cCuU]###/\.n_progress$/ потом в коде написать так @re = split(/###/, select_CU) и последовательно выполнить каждое рег выражение foreach my $re (@re) { if ($_ =~ m/$re/) { } } select_APL_LOG="grep -ie ^R -ie ^E -ie ^G -ie ^W | grep -e '^.001000.*' -e '^.100.*' -e '^.101.*' \ -e '^.103.*' -e '^.105.*' -e '^.106.*' -e '^.107.*' -e '^.108.*' \ -e '^.123.*' | grep -iv [.]in_progress$ " это переписывается так же как выше написано |
| Автор: amg 23.4.2008, 07:20 | ||
Сам скрипт мог бы выглядеть как то так (но я его не проверял!):
|
| Автор: ramus 23.4.2008, 22:36 | ||||||
2 tolkien
Да, интересная мысль. Главное просто и универсально. Попробую. Спасибо Вам. И с точки зрения производительности хорошо - при первом совпадении не надо проверять все оставшиеся варианты. 2 amq стараюсь внимательно читать Ваши посты - так как Вы пишете емко, но с минимумом комментариев. Многие решения нестандартны и интересны. Чувствуется большой опыт программирования. Естественно, то что Вы пишете Вам очевидно. Я с перл только начинаю дружить, поэтому без комментариев сложновато. Правильно я понял, что в регулярном выражении $select_APL_LOG = '(?!.*\.in_progress$)^(?i:[REGW])(?:001000|10[013])'; ?! - отрицание ?: - не захватывать () в $1 ? но вот сами три шаблона в скобках как работают не понятно
выводит Yes как и должно быть в кэмелбук таких примеров я не нашел...скорее плохо искал (все таки >1000стр.) там есть типа /^(regw).*out$/ . то есть в скобках m// один шаблон, а тут три . Они так объединяются по условию И ?
пока мой опыт переписывания shell скриптов (около десятка) на перл говорит, что перл рулит |
| Автор: ramus 23.4.2008, 23:04 | ||||||
| пространные рассуждения удалил для того, чтобы подгрузить процедуры и переменные из другого файла, надо сделать так файл mover.rules
основной файл
если запустить то выводит
|
| Автор: amg 26.4.2008, 11:20 | ||
Описание того, как вставлять различные логические условия в регулярное выражение, есть, например, в Perl Cookbook (с. 254 в переводе 2-го издания). Но пользоваться этим нужно лишь в исключительных случаях. Даже элементарное /a|b/ лучше заменять на /a/ || /b/ - как правило, быстрее будет работать, а для более сложных случаев (типа того, что ниже) - будет еще и гораздо понятнее.
(?!) - не просто отрицание, а "отрицательное" заглядывание вперед. Приведенное регулярное выражение пробегает по всем символам строки и смотрит, во-первых, чтобы строка начиналась на (?i:[REGW])(?:001000|10[013] и, во-вторых, чтобы перед каждым символом не было подстроки, оканчивающейся на \.in_progress. Понятно, что пока конец строки не достигнут, второе условие будет выполняться. Но когда встали на конец строки, тут то и появляется возможность определить, есть ли перед ним "подстрока, оканчивающаяся на \.in_progress". Естественно, такую извращенную логику я не сам придумал и реализовал - подсмотрел в Perl Cookbook |
| Автор: ramus 26.4.2008, 12:08 | ||||
Значит предложение tolkien (разбивать на несколько логических правил и последовательно выполнить каждое рег выражение) для данного случая подходит лучше. Кстати
например для двух правил по И select_CU=^[cCuU]#AND#/\.n_progress$/ для двух правил по ИЛИ select_CU=^[cCuU]#OR#/\.n_progress$/ но только как бы не нарваться на совпадение с самим шаблоном и необходимо придумать правило с приоритетами ( например в каком порядке выполнять 1 AND 2 OR 3 )? в общем направление решения мне понятно, буду реализовывать спасибо за разъяснения по RE за ссылку спасибо, почитаю |
| Автор: arto 26.4.2008, 16:56 | ||
а циферки привести можно? а то у меня: # perl -MBenchmark=timethese -de0 DB<1> @a = map { join "",map { qw(a b)[int rand 2] } 3..int(rand 10) } 1..102400 DB<2> timethese (10_000,{ 'A' => sub { my $a; foreach (@a) { $a += m#^a|^b# } return $a },'B' => sub { my $a; foreach (@a) { $a += m#^(?:a|b)# } return $a },'C' => sub { my $a; foreach (@a) { $a += m#^a# || m#^b# } return $a; } }) Benchmark: timing 10000 iterations of A, B, C... A: 705 wallclock secs (658.01 usr + 4.76 sys = 662.77 CPU) @ 15.09/s (n=10000) B: 683 wallclock secs (638.36 usr + 4.50 sys = 642.86 CPU) @ 15.56/s (n=10000) C: 795 wallclock secs (739.54 usr + 5.09 sys = 744.63 CPU) @ 13.43/s (n=10000) |
| Автор: amg 28.4.2008, 06:59 | ||||
| Я не очень понял, зачем это надо. Почему это не может быть нативный перловский код? mover.rules:
скрипт:
|
| Автор: amg 28.4.2008, 08:37 | ||||||
Пожалуйста.
arto, просто Вы обнаружили тот самый случай, который не подпадает под оговорку "как правило". У Вас регулярное выражение способно уже на первом символе строки решить, "Да" или "Нет". В моем же случае для этого решения ему нужно просмотреть всю весьма длинную строку до конца. Думаю, в моем случае результат сравнения будет совсем катастофическим, если для большого количества моделей воспользоваться приемом, который многие любят использовать:
|
| Автор: ramus 28.4.2008, 20:55 | ||||||
И пример красивый привели. Действительно перловый стиль. А я все шеллом мыслю :( С правилами отбора все ясно и просто Теперь думаю все ли случаи позволит реализовать предложенный Вами подход в "actions":
так как реально actions бывают весьма извращенны например такие два примера actions на шелле
требуемая бизнес логика бывает весьма извращенна |
| Автор: ramus 3.5.2008, 20:00 | ||||||||||||||||
| Блин есть файл правил (file_rules). В нем могут определяться абсолютно любые (отвечающие синтаксису перла) переменные и процедуры. ВАЖНО: Их имена в основной проге заранее неизвестны. Например:
Он зачитывается из основной проги как
Есть конфигурационный файл, в котором эти переменные и процедуры могут использоваться. Например
надо: из проги на перле, зачитав из конфигурационного файла строку, правильно ее разобрать. Например в данном случае строка:
должна быть оттранслирована в (если дата запуска проги 3 мая 2008)
пытаюсь сделать через eval
Error: while eval= - Illegal division by zero at (eval 4) line 1. тогда пытаюсь так (через выполнение регулярки /e )
ругается что
а я не могу написать в основной проге our $disk_T, так как я формально до ее запуска не знаю какие переменные навставляют в file_rules. Что сделать - ума не приложу не хотелось бы весь файл правил построчно проверять на наличие переменных и делать "our переменная", так как тогда надо разбирать что переменная объявляется не внутри процедур, а это совсем другое решение :( сделать два отдельных файла правил - для процедур и для переменных что то не хочется |
| Автор: ramus 3.5.2008, 21:18 | ||||
Вроде разобрался. Перед строкой
надо дать
|
| Автор: amg 4.5.2008, 15:36 | ||
А так?
|
| Автор: ramus 4.5.2008, 19:19 | ||||
попробовал
Ругается
Это и понятно, так как я загружаю файл file_rules через do или require, а там свое адресное пространство, хотя имя пакета main я не переопределяю. Если в текущем блоке сказать our переменная, то тогда видит. Либо попытаться реализовать do своими силами что то типа eval (`cat $file_rules`) то есть зачитать файл в текущем блоке. Я остановился на первом варианте - тоесть выбираю все переменные в строке для eval и объявляю их в текущем блоке - все замечательно работает и не надо свой анализатор кода изобретать |
| Автор: amg 5.5.2008, 07:11 |
| ramus, а так ли нужно в Вашем случае использовать прагму strict? У меня сложилось ощущение, что используя ее, Вы лишь создаете себе трудности, которые потом успешно преодолеваете. Т.е. она Вам скорее мешает, чем помогает. |
| Автор: ramus 5.5.2008, 19:24 | ||
Ну не знаю, тут привычка скорее. Просто в начале программирования на перл я без нее столько ошибок наплодил без понимания. Считаю, что в промышленном коде она должна быть. Да и обошел я ее. Тем не менее спасибо за помощь, ибо когда кончались идеи, новые я черпал здесь на сайте. |
| Автор: ramus 12.5.2008, 21:29 | ||||||||
| Уперся теперь в производительность :( есть директория с 20 000 файлов
проходит менее чем за 1 секунду. А вот проверка файл или директория (если директория, то добавить к имени файла "/") работает за 20-25 секунд. Код такой
да к тому же мне нужно отобрать около 80 файлов по условию находящемуся в переменной $files ( зачитывается из конфигурационного файла) $files=' ( $_ =~ m/^[rsa]4030/i ) && ( $_ !~ m{\/$} )'
этот блок еще работает секунд 12. на этом же серваке, в этой же директории команда shell ls -p $[dir_src} | grep -i '^[rsa]4030' | grep -v '/$' отрабатывает менее чем за 1 секунду что делать ? как ускорить? пробовал сделать так
ругается на синтаксис |
| Автор: amg 13.5.2008, 07:00 | ||
PS там в коде - опечатка if ( $_ =~ '^\.\.? ) |
| Автор: ramus 13.5.2008, 08:25 | ||||||
Да, опечатался. Набирал из дома. Правильный код if ( $_ =~ m/^\.\.?/ ) или if ( $_ =~ m/^\.{1,2}/ )
У меня тоже сервак древний. Возможно у меня задержки из за латентности, так как реально файловая система подцеплена по NFS с другого сервера и 20_000 проверок по сети делать дорого, вместо одного запроса. Буду экспериментировать. А вот как бы ускорить этот код, чтобы не делать 20_000 eval ?
Результаты эксперимента: на локальной FS время работы 1 секунда на все на FS по NFS время работы 1-2 секунды на чтение (readdir) + 6 !!! минут на проверку -d для 20_000 файлов правда на серваке сейчас идет работа и соответственно сильная загрузка... вчера было 20-25 секунд. |
| Автор: amg 13.5.2008, 10:31 | ||
На самом деле эти 20_000 eval не так уж сильно замедляют:
|
| Автор: ramus 13.5.2008, 11:01 | ||
у меня получилось 1/10, но 1 секунда против 10. сервак ОЧЕНЬ старый HP L1000, частота процессора всего 450 МГц. Видимо дело в этом. на персоналке выполняется в 10 раз быстрее. а за код с 1 eval спасибо, чтото сам не додумался :( А как все просто оказалось |