regex - 带有捕获组的正则表达式,用于由可变数量的单词组成的子字符串

标签 regex linux bash capture-group

使用以下Bash脚本(改编自this answer):

#!/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}"

我想匹配以下行(通过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(包装尺寸)
(空,因为后面没有文本)
但是,当我运行脚本时,没有匹配的内容。
这里是单独的regex(换行符只是为了可读性):
[1]
你能告诉我如何匹配一个字符串,比如^([[:alpha:]]+)[[:space:][:punct:]]+([[:alpha:][:space:]]+)[[:space:]]([[:digit:]+[mcg|mg|g][:space:][\/0-9a-zA-Z[:space:]]*])[\[]([[:digit:]]+)[\]]([[:alpha:]]*)$并在50mcg/1g 30 g中捕获它吗?

最佳答案

与您的previous question一样,awk提供了一个更易于维护、速度更快的解决方案:
awk是最好的选择,因为您的输入基本上是基于字段的,而将输入分解为字段是awk的亮点。要了解awk,请参阅系统上的awk POSIX spec或运行man awkinfo awk
为了简单起见,并与示例输入保持一致,所有行内空白都假定为空格;如果还应该匹配制表符,则将regex中的实例替换为[[: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
---

以下是您的purebash尝试的固定和简化版本:
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:]])的实例用于捕获成分和品牌名称;表达式捕获由空白分隔的纯字母单词的变量列表(列表中包含一个单字母单词)。
简化的regex通过使用mcg匹配品牌名称后面的所有内容,直到使用mg匹配下面的g(包大小的开始),从而避免了[/[^[]+解析困难,不管它包含多少空格;因为这包括尾随空格,read稍后将用于修剪。
如果确实需要显式匹配以排除误报:
mcg替换为mg
gindex[^[]+替换为([[:digit:].]+(mcg|mg|g)[/0-9a-zA-Z[:space:]]*),将$BASH_REMATCH替换为5,因为出于技术原因,上面引入了一个新的捕获组-请参阅下面的说明。
请注意如何使用6(匹配制表符或空格)来代替4,因为后者还匹配新行,根据定义,新行在此不存在。
您最初的尝试存在各种问题,其中一些问题已经由Benjamin W.在对该问题的评论中指出:
5应该是[:blank:][:space:],因为[mcg|mg|g]是一个bracket expression:在这种情况下,任何一个字符都匹配单个字符,这样实际上就匹配单个(mcg|mg|g)(mc?)?g[mcg|mg|g]m字符。
c使用非ASCIIfullwidth colons,Bash无法将其识别为字符类的一部分。
不是问题本身,而是警告和简化机会:
你混合了|,这只保证在ASCII范围内工作;与外文匹配,坚持g;反之,[:space:]可以假设匹配非ASCII数字,因此[:alpha:]可能是更安全的选择。
不需要在a-zA-Z中的[:alpha:]内转义[:digit:],因为[0-9]不是regex元字符,也不用作/中的regex分隔符。
[...]bash表示文字/bash是不必要的复杂;请使用[\[][\]]代替。
主要的问题是你似乎对括号表达式的工作方式有一个误解。例如,[是一个构造错误的单括号表达式,它应该是多个独立的子表达式:
]-一个括号表达式,用于匹配一系列数字和/或\[(例如,也用于匹配\])。
[[:digit:]+[mcg|mg|g][:space:][/0-9a-zA-Z[:space:]]*]一个括号化的子表达式(捕获组),使用交替< cc>匹配三个令牌中的任何一个;注意,在[[:digit:].]+正则表达式中使用.总是创建一个捕获组,即使只需要括号优先,那么当索引为0.5g时,您需要说明这个值。
(mcg|mg|g)-另一个括号表达式,它匹配由|字符、十进制数字、ASCII字母和空白字符组成的任何(可能为空)字符运行。
然后,连接这些子表达式时应匹配字符串,如(...),您可以按如下方式验证该字符串:
bash
有很好的在线工具可以可视化和调试正则表达式,它们也是很好的教学工具。一个例子是regex101.com
注意,这些工具通常不直接支持${BASH_REMATCH[@]}和各种Unix实用程序中的(通常是平台特定的)regex方言,但是选择[/0-9a-zA-Z[:space:]]*作为方言通常会提供一个超集。
需要注意的是,您需要知道您的特定实用程序支持哪些子集,否则您最终可能会得到一个只在联机测试仪中工作的regex。
/如何匹配50mcg/1g 30 g的演示可以找到here
Here是来自上述固定[[ '50mcg/1g 30 g' =~ [[:digit:].]+(mcg|mg|g)[/0-9a-zA-Z[:space:]]* ]] && echo "MATCHED: >>>${BASH_REMATCH[0]}<<<"溶液的完整regex,对照完整样本输入行进行测试。

关于regex - 带有捕获组的正则表达式,用于由可变数量的单词组成的子字符串,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42060065/

相关文章:

regex - 无论如何,我是否可以避免将以下规则中的 9 位数字与我拥有的正则表达式相匹配?

regex - 如何使用 bash 将每一行提取到不同的变量中

linux - 如何运行同一脚本的多个 perl 实例?

bash - 如何将传递给我的 Bash 脚本的所有参数传递给我的函数?

正则表达式匹配大写字符与小写搜索

regex - Perl 正则表达式匹配表情符号

ruby-on-rails - 如何在 Ruby on Rails 中将一组保留字符替换为 ""?

c++ - 在Linux上使用Makefile编译独立的ASIO

bash - 如何从 bash 或 unix 中的命令行检查 X509 公钥证书

linux - 按bash中的多列排序