具有由可变数量的单词组成的子串的捕获组的正则表达式

时间:2017-02-06 03:47:19

标签: 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(包装尺寸)
  • (空,因为[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 个答案:

答案 0 :(得分:1)

previous question的情况一样, awk提供了更易于维护且速度更快的解决方案

awk是最佳选择,因为您的输入基本上是 field - ,并且输入字段是awk闪耀的地方。要了解awk,请参阅awk POSIX spec或在您的系统上运行man awkinfo 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]bracket expression:在这种情况下,单个字符的字符,以便在您实际上匹配 mc,{{ 1}},或|字符。

  • g使用非ASCII fullwidth colons,Bash无法将其识别为角色类的一部分。

  • 本身不是问题,但需要注意和简化机会:

    • 你混合[:space:][:alpha:]只保证在ASCII范围内工作相同;为了匹配外国信,坚持a-zA-Z;相反,[:alpha:]可以假设匹配非ASCII数字,因此[:digit:]可能是更安全的选择。
    • 无需在[0-9]中的/内逃避[...],因为bash不是正则表达式字符串,也不会用作正则表达式分隔符 in /
    • bash[\[]代表文字[\]][不必要地复杂化;请改用]\[
  • 主要问题是您似乎对括号表达式的工作方式存在误解。例如,\]是一个构造不良的括号表达式,应该是多个独立的子表达式:

    • [[:digit:]+[mcg|mg|g][:space:][/0-9a-zA-Z[:space:]]*] - 一个括号表达式,用于匹配一组数字和/或[[:digit:].]+(例如也匹配.)。

    • 0.5g - 带括号的子表达式(捕获组),使用交替(mcg|mg|g)来匹配三个令牌中的任何一个;请注意,在|正则表达式中使用(...)总是会创建一个捕获组,即使您只需要优先级的括号,所以您需要在索引到bash时会考虑到这一点。

    • ${BASH_REMATCH[@]} - 另一个括号表达式,匹配由[/0-9a-zA-Z[:space:]]*,十进制数字,ASCII字母和空格字符组成的任何(可能为空)字符集。

    • 然后,加入这些子表达式应匹配/之类的字符串,您可以按如下方式进行验证:
      50mcg/1g 30 g

  • 用于可视化和调试正则表达式的强大在线工具,它们也是很棒的教学工具。一个例子是regex101.com

    • 请注意,这些工具通常不直接支持[[ '50mcg/1g 30 g' =~ [[:digit:].]+(mcg|mg|g)[/0-9a-zA-Z[:space:]]* ]] && echo "MATCHED: >>>${BASH_REMATCH[0]}<<<"和各种Unix实用程序中的(通常是平台特定的)正则表达式方言,但选择bash作为方言通常会提供超集
      需要注意的是,您需要知道您的特定实用程序支持哪个子集,否则您最终可能会使用仅适用于在线测试程序的正则表达式。

    • 可以找到PCRE[[:digit:].]+(mcg|mg|g)[\/0-9a-zA-Z[:space:]]*匹配的示例here

    • Here是上面针对完整示例输入行测试的固定50mcg/1g 30 g解决方案的完整正则表达式。