带有捕获组的正则表达式,用于由可变数量的单词组成的子字符串
Regex with capture groups for substrings made up of a variable number of words
使用以下 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]}
中捕获它吗?
与您的 一样,awk
提供更易于维护且速度更快的解决方案:
awk
是最好的选择,因为你的输入本质上是字段-based,将输入分解为字段是awk
闪耀的地方。要了解 awk
,请参阅系统上的 awk
POSIX spec 或 运行 man awk
或 info awk
。
为简单起见,并与样本输入一致,假设所有intra-line whitespace为spaces;如果制表符也应该匹配,则将正则表达式中的 </code> 实例替换为 <code>[[:blank:]]
。
awk -F' +- +|[][]' '
{
name = ; sub(" +[0-9.]+(mc?)?g.*", "", name)
strength = substr(, 1 + length(name)); sub("^ +", "", strength)
form = ""
if (NF > 3) { form = $NF; sub("^ +", "", form) }
print "Ingredient:",
print "Brand name:", name
print "Strength: ", strength
print "Pack size: ",
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:]])
的实例用于捕获成分和品牌名称;该表达式捕获由白色 space 分隔的 letter-only 个单词的可变列表(在列表中包含一个 2 个字母的单词)。
简化的正则表达式避免了 mcg
/ mg
/ g
解析困难,匹配品牌名称后的所有内容,直到以下 [
(包大小的开始)使用 [^[]+
,无论它包含多少 space;因为它包括尾随的白色space,read
后来被用来 trim 那。
- 如果您确实需要明确匹配
mcg
/ mg
/ g
以排除误报:
- 将
[^[]+
替换为([[:digit:].]+(mcg|mg|g)[/0-9a-zA-Z[:space:]]*)
- 将
$BASH_REMATCH
索引 5
替换为 6
,并将 4
替换为 5
,因为上面出于技术原因引入了一个新的捕获组 - 请参阅解释如下。
请注意如何使用 [:blank:]
(匹配制表符或 space)代替 [:space:]
,因为后者也匹配换行符,由此处没有定义。
您最初的尝试存在各种问题,其中一些问题已被Benjamin W.在问题评论中指出:
[mcg|mg|g]
应该是 (mcg|mg|g)
或 (mc?)?g
,因为 [mcg|mg|g]
是一个 bracket expression:在这种情况下,一个 set 个字符,其中任何一个匹配一个 单个 字符,因此实际上你匹配一个 单个 m
、c
、|
或 g
个字符。
[:space:]
使用 non-ASCII fullwidth colons,Bash 无法识别为一个字符 class.
本身不是问题,而是注意事项和简化机会:
- 您混合了
[:alpha:]
和 a-zA-Z
,它们只能保证在 ASCII 范围内工作相同;要匹配外文字母,请坚持使用 [:alpha:]
;相反,[:digit:]
可以假设匹配 non-ASCII 个数字,因此 [0-9]
可能是更安全的选择。
- 无需在
bash
中的 [...]
内转义 /
,因为 /
不是正则表达式元字符,也不用作正则表达式 定界符 in bash
.
[\[]
和 [\]]
来表示文字 [
和 ]
不必要地复杂;使用 \[
和 \]
代替。
主要问题是您似乎对括号表达式的工作原理有误解。例如,[[:digit:]+[mcg|mg|g][:space:][/0-9a-zA-Z[:space:]]*]
是一个 ill-constructed 单个 括号表达式,应该是多个独立的子表达式:
[[:digit:].]+
- 匹配 运行 个数字 and/or .
的括号表达式(也匹配 0.5g
,对于实例).
(mcg|mg|g)
- 带括号的子表达式(捕获组)使用交替 |
来匹配三个标记中的任何一个;请注意,在 bash
正则表达式 中使用 (...)
总是 创建一个捕获组,即使您只需要 precedence[=186= 的括号],因此在索引 ${BASH_REMATCH[@]}
.
时需要考虑到这一点
[/0-9a-zA-Z[:space:]]*
- 匹配任何(可能为空)运行 由 /
、十进制数字、ASCII 字母和 whitespace个字符.
加入这些子表达式应该会匹配到一个字符串如50mcg/1g 30 g
,可以如下验证:
[[ '50mcg/1g 30 g' =~ [[:digit:].]+(mcg|mg|g)[/0-9a-zA-Z[:space:]]* ]] && echo "MATCHED: >>>${BASH_REMATCH[0]}<<<"
有用于可视化和调试正则表达式的出色在线工具,它们也是出色的教学工具。一个例子是 regex101.com.
使用以下 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]}
中捕获它吗?
与您的 awk
提供更易于维护且速度更快的解决方案:
awk
是最好的选择,因为你的输入本质上是字段-based,将输入分解为字段是awk
闪耀的地方。要了解 awk
,请参阅系统上的 awk
POSIX spec 或 运行 man awk
或 info awk
。
为简单起见,并与样本输入一致,假设所有intra-line whitespace为spaces;如果制表符也应该匹配,则将正则表达式中的 </code> 实例替换为 <code>[[:blank:]]
。
awk -F' +- +|[][]' '
{
name = ; sub(" +[0-9.]+(mc?)?g.*", "", name)
strength = substr(, 1 + length(name)); sub("^ +", "", strength)
form = ""
if (NF > 3) { form = $NF; sub("^ +", "", form) }
print "Ingredient:",
print "Brand name:", name
print "Strength: ", strength
print "Pack size: ",
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:]])
的实例用于捕获成分和品牌名称;该表达式捕获由白色 space 分隔的 letter-only 个单词的可变列表(在列表中包含一个 2 个字母的单词)。简化的正则表达式避免了
mcg
/mg
/g
解析困难,匹配品牌名称后的所有内容,直到以下[
(包大小的开始)使用[^[]+
,无论它包含多少 space;因为它包括尾随的白色space,read
后来被用来 trim 那。- 如果您确实需要明确匹配
mcg
/mg
/g
以排除误报:- 将
[^[]+
替换为([[:digit:].]+(mcg|mg|g)[/0-9a-zA-Z[:space:]]*)
- 将
$BASH_REMATCH
索引5
替换为6
,并将4
替换为5
,因为上面出于技术原因引入了一个新的捕获组 - 请参阅解释如下。
- 将
- 如果您确实需要明确匹配
请注意如何使用
[:blank:]
(匹配制表符或 space)代替[:space:]
,因为后者也匹配换行符,由此处没有定义。
您最初的尝试存在各种问题,其中一些问题已被Benjamin W.在问题评论中指出:
[mcg|mg|g]
应该是(mcg|mg|g)
或(mc?)?g
,因为[mcg|mg|g]
是一个 bracket expression:在这种情况下,一个 set 个字符,其中任何一个匹配一个 单个 字符,因此实际上你匹配一个 单个m
、c
、|
或g
个字符。[:space:]
使用 non-ASCII fullwidth colons,Bash 无法识别为一个字符 class.本身不是问题,而是注意事项和简化机会:
- 您混合了
[:alpha:]
和a-zA-Z
,它们只能保证在 ASCII 范围内工作相同;要匹配外文字母,请坚持使用[:alpha:]
;相反,[:digit:]
可以假设匹配 non-ASCII 个数字,因此[0-9]
可能是更安全的选择。 - 无需在
bash
中的[...]
内转义/
,因为/
不是正则表达式元字符,也不用作正则表达式 定界符 inbash
. [\[]
和[\]]
来表示文字[
和]
不必要地复杂;使用\[
和\]
代替。
- 您混合了
主要问题是您似乎对括号表达式的工作原理有误解。例如,
[[:digit:]+[mcg|mg|g][:space:][/0-9a-zA-Z[:space:]]*]
是一个 ill-constructed 单个 括号表达式,应该是多个独立的子表达式:[[:digit:].]+
- 匹配 运行 个数字 and/or.
的括号表达式(也匹配0.5g
,对于实例).(mcg|mg|g)
- 带括号的子表达式(捕获组)使用交替|
来匹配三个标记中的任何一个;请注意,在bash
正则表达式 中使用(...)
总是 创建一个捕获组,即使您只需要 precedence[=186= 的括号],因此在索引${BASH_REMATCH[@]}
. 时需要考虑到这一点
[/0-9a-zA-Z[:space:]]*
- 匹配任何(可能为空)运行 由/
、十进制数字、ASCII 字母和 whitespace个字符.加入这些子表达式应该会匹配到一个字符串如
50mcg/1g 30 g
,可以如下验证:
[[ '50mcg/1g 30 g' =~ [[:digit:].]+(mcg|mg|g)[/0-9a-zA-Z[:space:]]* ]] && echo "MATCHED: >>>${BASH_REMATCH[0]}<<<"
有用于可视化和调试正则表达式的出色在线工具,它们也是出色的教学工具。一个例子是 regex101.com.