Захват групп из регулярного выражения Grep
у меня есть этот маленький скрипт в sh
(Mac OSX 10.6) для просмотра массива файлов. Google перестал быть полезным в этот момент:
files="*.jpg"
for f in $files
do
echo $f | grep -oEi '[0-9]+_([a-z]+)_[0-9a-z]*'
name=$?
echo $name
done
пока (очевидно, вам оболочка гуру)$name
просто содержит 0, 1 или 2, в зависимости от if grep
обнаружил, что имя файла соответствует указанному вопросу. что я хотел бы, чтобы захватить то, что внутри parens ([a-z]+)
и сохраните это в переменной.
Я хочу использовать grep
только, если возможно. Если нет, пожалуйста, не Python или Perl и т. д. sed
или что – то вроде этого-я новичок в shell и хотел бы атаковать это с точки зрения пуриста *nix.
также, как супер-круто бонуs, Мне любопытно, как я могу объединить строку в оболочке? Группа, которую я захватил, была строкой "somename", хранящейся в $name, и я хотел добавить строку ".jpg " до конца, не мог бы я cat $name '.jpg'
?
Пожалуйста, объясните, что происходит, если у вас есть время.
7 ответов
если вы используете Bash, вам даже не нужно использовать grep
:
files="*.jpg"
regex="[0-9]+_([a-z]+)_[0-9a-z]*"
for f in $files
do
if [[ $f =~ $regex ]]
then
name="${BASH_REMATCH[1]}"
echo "${name}.jpg" # concatenate strings
name="${name}.jpg" # same thing stored in a variable
else
echo "$f doesn't match" >&2 # this could get noisy if there are a lot of non-matching files
fi
done
лучше поместить регулярное выражение в переменной. Некоторые паттерны не будут работать, если включить их буквально.
использует =~
который является оператором соответствия регулярных выражений Bash. Результаты совпадения сохраняются в массиве $BASH_REMATCH
. Первая группа захвата хранится в индексе 1,вторая (если есть) в индексе 2 и т. д. Индекс ноль-это полное совпадение.
вы должны знать, что без якоря, это регулярное выражение (и тот, который использует grep
) будет соответствовать любому из следующих примеров и более, что может быть не то, что вы ищете:
123_abc_d4e5
xyz123_abc_d4e5
123_abc_d4e5.xyz
xyz123_abc_d4e5.xyz
чтобы исключить второй и четвертый примеры, сделайте свое регулярное выражение следующим образом:
^[0-9]+_([a-z]+)_[0-9a-z]*
который говорит, что строка должна старт С одной или более цифр. Карат представляет собой начало строки. Если вы добавите знак доллара в конце регулярного выражения, например:
^[0-9]+_([a-z]+)_[0-9a-z]*$
затем третий пример также будет исключен, так как точка не входит в число символов в регулярном выражении, а знак доллара представляет конец строки. Обратите внимание, что четвертый пример также не соответствует этому матчу.
если у вас есть GNU grep
(около 2.5 или позже, я думаю, когда \K
добавлен оператор):
name=$(echo "$f" | grep -Po '(?i)[0-9]+_\K[a-z]+(?=_[0-9a-z]*)').jpg
на \K
оператор (переменная длина look-behind) вызывает совпадение предыдущего шаблона, но не включает совпадение в результат. Этот фиксированной длины, эквивалентной (?<=)
- схемы будет включен перед закрывающей скобкой. Вы должны использовать \K
если кванторы могут соответствовать строкам разной длины (например,+
, *
, {2,4}
).
на (?=)
оператор соответствует шаблонам фиксированной или переменной длины и называется "Взгляд вперед". Он также не включает соответствующую строку в результат.
для того, чтобы сделать нечувствительным к регистру матч, оператор. Это влияет модели, которые следуют за ним, поэтому его положение является значительным.
регулярное выражение может потребоваться настроить в зависимости от того, есть ли другие символы в имени файла. Вы заметите, что в этом случае я показываю пример объединения строки одновременно с захватом подстроки.
это действительно невозможно с pure grep
, по крайней мере, не обычно.
но если ваш шаблон подходит, вы можете использовать grep
несколько раз в конвейере, чтобы сначала уменьшить строку до известного формата, а затем извлечь только бит, который вы хотите. (Хотя инструменты вроде cut
и sed
гораздо лучше).
предположим, ради аргумента, что ваш рисунок был немного проще: [0-9]+_([a-z]+)_
вы можете извлечь это, как Итак:
echo $name | grep -Ei '[0-9]+_[a-z]+_' | grep -oEi '[a-z]+'
первый grep
удалит любые строки, которые не соответствуют вашему общему патерну, второй grep
(имеющего --only-matching
указано) отобразит Альфа-часть имени. Это работает только потому, что шаблон подходит: "Альфа-часть" достаточно специфична, чтобы вытащить то, что вы хотите.
(в сторону: лично я использую grep
+ cut
чтобы достичь того, что вы после:echo $name | grep {pattern} | cut -d _ -f 2
. Это получает cut
для разбора строки на поля путем разбиения на разделитель _
, и возвращает только 2 поля (номера полей начинаются с 1)).
философия Unix состоит в том, чтобы иметь инструменты, которые делают одну вещь, и делают это хорошо, и объединяют их для достижения нетривиальных задач, поэтому я бы сказал, что grep
+ sed
etc-это более Unixy способ делать вещи : -)
Я понимаю, что ответ уже был принят для этого, но с "строго *Nix purist angle" кажется, что правильным инструментом для работы является pcregrep
, который, кажется, еще не упоминался. Попробуйте изменить строки:
echo $f | grep -oEi '[0-9]+_([a-z]+)_[0-9a-z]*'
name=$?
следующим образом:
name=$(echo $f | pcregrep -o1 -Ei '[0-9]+_([a-z]+)_[0-9a-z]*')
чтобы получить только содержимое группы захвата 1.
на pcregrep
инструмент использует все тот же синтаксис, который вы уже использовали с grep
, но реализует функциональность, которая вам нужна.
параметр -o
работает так же, как grep
версия, если она голая, но она также принимает числовой параметр в pcregrep
, что указывает, какую группу захвата вы хотите показать.
С помощью этого решения в скрипте требуется минимум изменений. Вы просто заменяете одну модульную утилиту на другую и настраиваете параметры.
Интересное Замечание: вы можете использовать несколько аргументов-o для возврата нескольких групп захвата в том порядке, в котором они появляются в строке.
невозможно только в grep я считаю
для sed:
name=`echo $f | sed -E 's/([0-9]+_([a-z]+)_[0-9a-z]*)|.*//'`
Я возьму удар на бонус, хотя:
echo "$name.jpg"
это решение, которое использует gawk. Это то, что мне нужно часто использовать, поэтому я создал для него функцию
function regex1 { gawk 'match(,/''/, ary) {print ary['${2:-'1'}']}'; }
использовать просто do
$ echo 'hello world' | regex1 'hello\s(.*)'
world
предложение для вас - вы можете использовать расширение параметра, чтобы удалить часть имени из последнего подчеркивания и далее, и аналогично в начале:
f=001_abc_0za.jpg
work=${f%_*}
name=${work#*_}
затем name
будет иметь значение abc
.
См. Apple разработчик документов, поиск вперед для расширения параметр.
Если у вас есть bash, вы можете использовать расширенный globbing
shopt -s extglob
shopt -s nullglob
shopt -s nocaseglob
for file in +([0-9])_+([a-z])_+([a-z0-9]).jpg
do
IFS="_"
set -- $file
echo "This is your captured output : "
done
или
ls +([0-9])_+([a-z])_+([a-z0-9]).jpg | while read file
do
IFS="_"
set -- $file
echo "This is your captured output : "
done