0

使用以下 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]}吗?

4

1 回答 1

1

与您之前的问题一样,awk提供了一个更易于维护且速度更快的解决方案

awk是最好的选择,因为您的输入本质上是基于字段的,并且将输入分解为字段是awk亮点。要了解awk,请参阅awkPOSIX 规范或在您的系统上运行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//以排除误报 mgg
      • 替换[^[]+([[:digit:].]+(mcg|mg|g)[/0-9a-zA-Z[:space:]]*)
      • $BASH_REMATCHindex5替换为645因为上面出于技术原因引入了一个新的捕获组 - 请参阅下面的说明。
  • 请注意[:blank:](匹配制表符或空格)如何代替[:space:],因为后者也匹配换行符,根据定义,此处不存在。


您最初的尝试存在各种问题,Benjamin W.在对该问题的评论中已经指出了其中一些问题:

  • [mcg|mg|g]应该是(mcg|mg|g)or (mc?)?g,因为[mcg|mg|g]括号表达式:在这种情况下,一字符匹配单个字符,因此实际上匹配单个 m, c, |, org字符。

  • [:space:]使用非 ASCII全角 冒号,Bash 不将其识别为字符类的一部分。

  • 不是问题本身,而是警告和简化机会:

    • 您正在混合[:alpha:]a-zA-Z并且只能保证在 ASCII 范围内工作相同;也匹配外国字母,坚持使用[:alpha:]; 相反,[:digit:]可以假设匹配非 ASCII 数字,因此[0-9]可能是更安全的选择。
    • 不需要/[...]in中转义bash,因为/它不是正则表达式元字符,也没有用作 正则表达式分隔bash
    • [\[]并且[\]]表示字面意思[并且]不必要地复杂;使用\[and\]代替。
  • 主要问题是您似乎对括号表达式的工作方式有误解。例如,[[: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,您可以验证如下:
      [[ '50mcg/1g 30 g' =~ [[:digit:].]+(mcg|mg|g)[/0-9a-zA-Z[:space:]]* ]] && echo "MATCHED: >>>${BASH_REMATCH[0]}<<<"

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

    • 请注意,这些工具通常不直接支持(通常是特定于平台的)正则表达式方言bash和各种 Unix 实用程序,但选择PCRE作为方言通常提供superset
      需要注意的是,您需要知道您的特定实用程序支持哪些子集,否则您最终可能会得到一个仅在在线测试器中有效的正则表达式。

    • 此处展示了如何找到[[:digit:].]+(mcg|mg|g)[\/0-9a-zA-Z[:space:]]*匹配项。50mcg/1g 30 g

    • bash是上面针对完整样本输入行测试的固定解决方案的完整正则表达式。

于 2017-02-06T06:55:46.657 回答