Регулярное выражение с группами захвата для подстрок, состоящих из переменного числа слов
С помощью следующего скрипта Bash (адаптированного из этого ответа):
#!/bin/bash
while IFS= read -r line || [[ -n "$line" ]]; do
if [[ "$line" =~ ^([[:alpha:]]+)[[:space:][:punct:]]+([[:alpha:][:space:]]+)[[:space:]]([[:digit:]+[mcg|mg|g][:space:][\/0-9a-zA-Z[:space:]]*])[\[]([[:digit:]]+)[\]]([[:alpha:]]*)$ ]]
then
printf "Ingredient: %s\n" "${BASH_REMATCH[1]}"
printf "Brand name: %s\n" "${BASH_REMATCH[2]}"
printf "Strength: %s\n" "${BASH_REMATCH[3]}"
printf "Pack size: %s\n" "${BASH_REMATCH[4]}"
printf "Form: %s\n" "${BASH_REMATCH[5]}"
fi
done < "${1:-/dev/stdin}"
Я хочу сопоставить строки, такие как следующие (предоставляется через стандартный ввод или через файл, переданный в качестве первого аргумента):
Calcipotriol - Daivonex Cream 50mcg/1g 30 g [1]
Candesartan cilexetil - Atacand 4mg [30] capsule
Danazol - Azol 100mg [100]
Dexamethasone - Dexmethsone 0.5g [1] tablet
и разбить их на 4-5 полей.
Например, строка Calcipotriol - Daivonex Cream 50mcg/1g 30 g [1]
следует разбить на поля следующим образом:
Calcipotriol
(ингредиент)Daivonex Cream
(название бренда)50mcg/1g 30 g
(прочность)1
(размер упаковки)- (пусто, потому что текст не следует
[1]
) (форма)
Однако, когда я запускаю свой скрипт, ничего не совпадает.
Вот регулярное выражение в отдельности (разрывы строк только для удобства чтения):^([[:alpha:]]+)[[:space:][:punct:]]+([[:alpha:][:space:]]+)[[:space:]]([[:digit:]+[mcg|mg|g][:space:][\/0-9a-zA-Z[:space:]]*])[\[]([[:digit:]]+)[\]]([[:alpha:]]*)$
Можете ли вы сказать мне, как сопоставить строку, такую как 50mcg/1g 30 g
и захватить его в ${BASH_REMATCH[4]}
?
1 ответ
Как и в случае с вашим предыдущим вопросом, awk
предлагает более легкое в обслуживании и гораздо более быстрое решение:
awk
это лучший выбор, потому что ваш ввод по существу основан на полях, а разбивка ввода на поля - это то, где awk
светит. Узнать о awk
см awk
POSIX спецификация или запуск man awk
или же info awk
в вашей системе.
Для простоты и в соответствии с образцом ввода все пробелы внутри строки предполагаются пробелами; заменить случаи в регулярных выражениях с
[[:blank:]]
если вкладки тоже должны совпадать.
awk -F' +- +|[][]' '
{
name = $2; sub(" +[0-9.]+(mc?)?g.*", "", name)
strength = substr($2, 1 + length(name)); sub("^ +", "", strength)
form = ""
if (NF > 3) { form = $NF; sub("^ +", "", form) }
print "Ingredient:", $1
print "Brand name:", name
print "Strength: ", strength
print "Pack size: ", $3
print "Form: ", form
print "---"
}
' <<'EOF'
Calcipotriol - Daivonex Cream 50mcg/1g 30 g [1]
Candesartan cilexetil - Atacand 4mg [30] capsule
Danazol - Azol 100mg [100]
Dexamethasone - Dexmethsone 0.5g [1] tablet
EOF
выходы:
Ingredient: Calcipotriol
Brand name: Daivonex Cream
Strength: 50mcg/1g 30 g
Pack size: 1
Form:
---
Ingredient: Candesartan cilexetil
Brand name: Atacand
Strength: 4mg
Pack size: 30
Form: capsule
---
Ingredient: Danazol
Brand name: Azol
Strength: 100mg
Pack size: 100
Form:
---
Ingredient: Dexamethasone
Brand name: Dexmethsone
Strength: 0.5g
Pack size: 1
Form: tablet
---
Вот исправленная и упрощенная версия вашего чистого bash
попытка:
while IFS= read -r line || [[ -n "$line" ]]; do
if [[ "$line" =~ ^([[:alpha:]][[:alpha:][:blank:]]*[[:alpha:]])[[:blank:][:punct:]]+([[:alpha:]][[:alpha:][:blank:]]*[[:alpha:]])[[:blank:]]+([^[]+)\[([0-9]+)\][[:blank:]]*([[:alpha:]]*)$ ]]
then
printf "Ingredient: %s\n" "${BASH_REMATCH[1]}"
printf "Brand name: %s\n" "${BASH_REMATCH[2]}"
read -r strength <<<"${BASH_REMATCH[3]}"
printf "Strength: %s\n" "$strength"
printf "Pack size: %s\n" "${BASH_REMATCH[4]}"
printf "Form: %s\n" "${BASH_REMATCH[5]}"
fi
done < "${1:-/dev/stdin}"
Экземпляры
([[:alpha:]][[:alpha:][:blank:]]*[[:alpha:]])
используются для захвата ингредиента и торговой марки; выражение захватывает переменный список буквенных слов, разделенных пробелами (содержащий в списке одно 2-буквенное слово).Упрощенное регулярное выражение избегает
mcg
/mg
/g
Сложность разбора путем сопоставления всего после имени бренда до следующего[
(начало размера пакета) с помощью[^[]+
сколько бы мест в нем не было; так как это включает в себя конечные пробелы,read
позже используется, чтобы урезать это.- Если вам нужно соответствовать
mcg
/mg
/g
явно, чтобы исключить ложные срабатывания:- замещать
[^[]+
с([[:digit:].]+(mcg|mg|g)[/0-9a-zA-Z[:space:]]*)
- замещать
$BASH_REMATCH
индекс5
с6
, а также4
с5
потому что вышеизложенное вводит новую группу захвата по техническим причинам - см. пояснение ниже.
- замещать
- Если вам нужно соответствовать
Обратите внимание, как
[:blank:]
(соответствие табуляции или пробела) используется вместо[:space:]
потому что последний также соответствует символам новой строки, которых по определению здесь нет.
Существуют различные проблемы с вашей первоначальной попыткой, некоторые из которых уже были отмечены Benjamin W.. в комментариях к вопросу:
[mcg|mg|g]
должно быть(mcg|mg|g)
или же(mc?)?g
, так как[mcg|mg|g]
является выражением в скобках: в данном случае, набор символов, любой из которых соответствует одному символу, так что в действительности вы соответствуете одномуm
,c
,|
, или жеg
персонаж.[:space:]
использует двоичные двоеточия без ASCII, которые Bash не распознает как часть класса символов.Не проблемы как таковые, но предостережения и возможности упрощения:
- Вы смешиваете
[:alpha:]
а такжеa-zA-Z
которые гарантированно работают одинаково в диапазоне ASCII; чтобы соответствовать иностранному письму, придерживайтесь[:alpha:]
; наоборот,[:digit:]
может гипотетически соответствовать не-ASCII цифрам, поэтому[0-9]
может быть более безопасный выбор. - Не нужно убегать
/
внутри[...]
вbash
, так как/
не является метасимволом регулярных выражений и также не используется в качестве разделителя регулярных выражений вbash
, [\[]
а также[\]]
представлять буквально[
а также]
излишне сложно; использование\[
а также\]
вместо.
- Вы смешиваете
Основная проблема заключается в том, что вы, похоже, неправильно понимаете, как работают выражения в скобках. Например,
[[:digit:]+[mcg|mg|g][:space:][/0-9a-zA-Z[:space:]]*]
является неправильно построенным выражением с одной скобкой, которое должно быть несколькими независимыми подвыражениями:[[:digit:].]+
- выражение в скобках для соответствия серии цифр и / или.
(также соответствовать0.5g
, например).(mcg|mg|g)
- заключенное в скобки подвыражение (группа захвата) с использованием чередования|
соответствовать любому из трех токенов; обратите внимание, что с помощью(...)
вbash
regex неизменно создает группу захвата, даже если вам нужны только круглые скобки для приоритета, поэтому вы должны учитывать это при индексации в${BASH_REMATCH[@]}
,[/0-9a-zA-Z[:space:]]*
- другое выражение в скобках, которое соответствует любой (потенциально пустой) серии символов, состоящей из/
, десятичные цифры, буквы ASCII и пробельные символы.Присоединение к этим подвыражениям должно соответствовать строке, такой как
50mcg/1g 30 g
, который вы можете проверить следующим образом:[[ '50mcg/1g 30 g' =~ [[:digit:].]+(mcg|mg|g)[/0-9a-zA-Z[:space:]]* ]] && echo "MATCHED: >>>${BASH_REMATCH[0]}<<<"
Есть отличные онлайн-инструменты для визуализации и отладки регулярных выражений, которые также являются отличными инструментами обучения. Одним из примеров является https://regex101.com/.
Обратите внимание, что эти инструменты обычно не поддерживают (часто для платформы) диалекты регулярных выражений, найденные в
bash
и различные утилиты Unix, но выбираяPCRE
как диалект обычно обеспечивает надмножество.
Предупреждение состоит в том, что вам нужно знать, какое подмножество поддерживает ваша конкретная утилита, в противном случае вы можете получить регулярное выражение, которое работает только в онлайн-тестере.Демонстрация того, как
[[:digit:].]+(mcg|mg|g)[\/0-9a-zA-Z[:space:]]*
Матчи50mcg/1g 30 g
можно найти здесь.Вот полное регулярное выражение из фиксированного
bash
Решение, приведенное выше, проверено на полной линии ввода образца.