Построение регулярного выражения по списку строк
Опубликовано 18.08.2009
Имеется неколько десятков однотипных файлов вида FILE20090801011253.txt
, FILE20090801023619.txt
и т.д. Требуется составить регулярное выражение, которому удовлетворяют только названия файлов из списка.
Вручную это можно сделать примерно так:
FILE200908010(11253|23619)\.txt
Если файлов много, то никаких нервов не хватит высчитывать, проверять и перепроверять.
То же самое можно сделать полуавтоматически, с помощью механизма complete-into-braces оболочки bash. Сочетание клавиш Esc-{ преобразовывает список подстановки в более-менее оптимальный формат brace-completion, пригодный для дальнейшей обработки:
$ echo /path/to/dir/(Esc-{)FILE200908010{11253.txt,23619.txt}
Чтобы из получившейся строки сделать нормальный регэксп, нужно фигурные скобки заменить на круглые, а запятые — на вертикальную черту:
echo "FILE200908010{11253.txt,23619.txt} "|sed -e 's/{/(/g' -e 's/}/)/g' -e 's/,/|/g'
Приходится делать много лишних слабоавтоматизируемых действий, после чего копировать, вставлять, заменять и исправлять, что не очень удобно.
После непродолжительных поисков был найден Perl’овый модуль Regexp::List, функцию list2re (преобразование списка в регулярное выражение) из которого можно приспособить под любые подобные задачи. Вот, например, сокращенная версия скрипта для моего случая:
use Regexp::List; my $l=Regexp::List->new; $l->set(lookahead=>0); opendir(D,"/path/to/dir") or die "Could not open $dir: $!"; my @list=grep {$_!~/^\.\.?$/ } readdir(D); # get directory entries except "." and ".." closedir(D); my $re = $l->list2re(@list); # create regexp from @list $re=~s/^\(\?-[xism]+:(.*?)\)$/^$1\$/g; # strip "(?:-xism" and ")" print "$re\n";
Для следующих десяти файлов:
FILE20090802120343.txt FILE20090802165139.txt FILE20090802181550.txt FILE20090804014529.txt FILE20090804140848.txt FILE20090805103525.txt FILE20090805104025.txt FILE20090810083211.txt FILE20090810120349.txt FILE20090810121250.txt
Скрипт выдает такой результат:
^FILE200908(?:0(?:510(?:40|35)25\.txt|21(?:81550|20343|65139)\.txt|4(?:140848|014529)\.txt)|10(?:12(?:1250|0349)\.txt|083211\.txt))$
Что и требовалось.
Расскажите как мне использовать регулярки в консоли, например для проставления хозяина на скрытые фалйы:
# chown -R www:www .*
Тогда под шаблон попадает и «..», что нам не хотелось бы вовсе…
Я думаю шаблон должен быть следующего вида:
# chown -R www:www [^\.]\.*
И еще я юзаю csh, если bash это умеет делать, то скажите, попробую его наконец-то.
Например, так:
Или так:
А нормальной поддержки регулярных выражение для файловых масок нет?
Нормально это:
# chown -R www:www [^\.]\.*
Использование find+xargs я считаю костылем, потому что нет нормальной поддержки регулярок для файловых масок.
Не костыль — это dotglob, аналогов которому в csh я навскидку не нашел.
А несовпадение dotfiles с шаблоном *, равно как и совпадение папок . и .. с шаблоном .* обосновано стандартом POSIX Patterns Used for Filename Expansion, поэтому приходится извращаться разными способами.
Расширенная поддержка регэкспов в globbing может усугубить несовместимость шеллов со стандартами, поэтому ее и не торопятся вводить. Хотя в третьем bash уже имеются кое-какие расширения в globbing в эту сторону.
Ясно, спасибо!
А для чего эта строка?
$re=~s/^\(\?-[xism]+:(.*?)\)$/^$1\$/g;
Regexp::List добавляет в начало каждой группы немного дополнительных модификаторов, в результате получается (?-xism:regexp). Поскольку мне эти регулярки нужны для использования не в перл, то я от них избавляюсь.
За ‘Esc-{‘ отдельное спасибо!