shell regex:提取价格

时间:2015-12-17 02:48:23

标签: regex shell awk sed grep

鉴于以下价格清单,我试图弄清楚如何规范化/仅提取数字。

INPUT          DESIRED_OUTPUT

CA$1399.00     1399.00
$1399.11   1399.11
$1,399.22<     1399.22
Z$1 399.33     1399.33
$1399.44#      1399.44
C$ 1399.55     1399.55
1,399.66       1399.66
1399.77        1399.77
,1399.88       1399.88
25 1399.88     1399.88
399.99          399.99
88.88 99.99      99.99 (if >2 matches on one line, only the last one matters)
.1399.88         DO NOT MATCH (not a price; too many ".")
666.000          DO NOT MATCH (not a price: too many 0's)

我认为开始的是一个好主意,就是他们都有共同点:

  • 价格始终包含.NN,但从不包含.NNN

经过进一步检查,其他规则变得明显:

  • .NN前面必须有一个或多个digits
  • NNN.NN前面可以是,或简单的digit,但没有别的。
  • .NN之前和*N.NN之前的任何内容都标志着比赛的结束。
  • 最后,正则表达式需要考虑像1,399.661399.66)之类的逗号,以确定它是否是一个价格,然后剥离它们。例如1, 399.66不等于1399.66:它应该是399.66

我正在寻找sedgrepawk,以获得便携,高效的解决方案。我该如何解决这个问题?

我找到了similar question,但我不知道如何使用sed尝试以下正则表达式:

^\d+(,\d{1,2})?$

编辑:是的,我的输入格式可能有点奇怪,因为它是拼接页面连接的结果。

3 个答案:

答案 0 :(得分:1)

您可以使用以下shell脚本:

#/bin/sh
grep -v '\.\d\+\.' | # get rid of lines with multiple dots within the same number
grep -v '\.\d\d\d\+' | # get rid of lines with more than 2 digits after .
sed -e 's/\(.*\.[0-9][0-9]\).*$/\1/' | # remove anything after last .NN
sed -e 's/^.* \([0-9][0-9][0-9][0-9]\)\./\1./' | # "* NNNN." => "NNNN."
sed -e 's/^.* \([0-9][0-9]\)\./\1./' | # "* NN." => "NN."
sed -e 's/^.* \([0-9]\)\./\1./' | # "* N." => "N."
sed -e 's/^\(.*\)[ ,]\(\([0-9]\)\{3,\}\)\./\1\2./g' | # "*,NNN." or "* NNN." => "*NNN."
sed -e 's/^\(.*\)[ ,]\(\([0-9]\)\{6,\}\)\./\1\2./g' | # "*,NNNNNN." or "* NNNNNN." => "*NNNNNN."
sed -e 's/^\(.*\)[ ,]\(\([0-9]\)\{9,\}\)\./\1\2./g' | # "*,NNNNNNNNN." or "* NNNNNNNNN." => "*NNNNNNNNN."
grep -o '\d\+\.\d\d' # print only the price

如果数字由空格分隔或{3}组成,,则此解决方案最多可在.之前的9位数字处理。如果你需要提取更大的价格,只需添加更多行,将正则表达式中的数字增加3.; - )

将其放入名为extract_prices的文件中,使其可执行(chmod +x extract_prices)并运行它:./extract_prices < my_list.txt

使用以下输入在OS X上测试:

CA$1399.00
&#36;1399.11
$1,399.22<
Z$1 399.33
Z$12 777 666.34   # <-- additonal monster price
$1399.44#
C$ 1399.55
1,399.66
1399.77
,1399.88
25 1399.88
399.99
88.88 99.99
.1399.88
666.000

生成以下输出:

1399.00
1399.11
1399.22
1399.33
12777666.34
1399.44
1399.55
1399.66
1399.77
1399.88
1399.88
399.99
99.99

答案 1 :(得分:0)

awk 的解决方案,它会拆分所有不是数字或小数点的字符,并打印与价格匹配的最后一个字段。领先的 sed 脚本处理异常情况#3,其中我们有空格而不是标记数千点的逗号。

sed -e 's/  / x /g; :a; s/\(\$[1-9][0-9]*\) /\1/; ta' | awk -F '[^0-9.]' -v p='[0-9]+\\.[0-9][0-9]' '$0 ~ p { gsub(/,/, ""); for (i=NF; i>0; i--) if ($i ~ "^" p "$") { print $i; next } }'

注意:

1) sed 脚本使用测试进行迭代;因此,它可以处理数百万,数十亿等 2) sed 脚本还处理多个空格条件,使得$ 1 [] [] 1000.00最终不会变成$ 11000.00。 3)逗号被简单地删除/忽略...如果数字有逗号分隔的问题,可以通过删除 awk 脚本中的gsub并修复过滤器来解决问题领先 sed 脚本

这是一个更复杂的版本,它基于注释#3中的想法,只有当空格或逗号位于千位分隔符时才能使逗号和空格成为数字的一部分。

sed -e ':a; s/\(\$[1-9][0-9]*\) \([0-9][0-9][0-9][ .]\)/\1\2/; ta; :b; s/\([1-9][0-9]*\),\([0-9][0-9][0-9][,.]\)/\1\2/; tb;' | awk -F '[^0-9.]' -v p='[0-9]+\\.[0-9][0-9]' '$0 ~ p { for (i=NF; i>0; i--) if ($i ~ "^" p "$") { print $i; next } }'

如果每一行成功的可能性很高,那么摆脱“p”将会产生更有效的脚本。

sed -e ':a; s/\(\$[1-9][0-9]*\) \([0-9][0-9][0-9][ .]\)/\1\2/; ta; :b; s/\([1-9][0-9]*\),\([0-9][0-9][0-9][,.]\)/\1\2/; tb;' | awk -F '[^0-9.]' '{ for (i=NF; i>0; i--) if ($i ~ /^[0-9]+\.[0-9][0-9]$/) { print $i; next } }'

最后,为了安全起见,我们可以检查 sed 过滤器,以确保在进行替换之前我们有一个有效的空格或逗号分隔的数字。

sed -e ':a; /\$[1-9][0-9]\?[0-9]\?\( [0-9][0-9][0-9]\)\+\.[0-9][0-9]/ s/\(\$[1-9][0-9]*\) \([0-9][0-9][0-9][ .]\)/\1\2/; ta; :b; /[1-9][0-9]\?[0-9]\?\(,[0-9][0-9][0-9]\)\+\.[0-9][0-9]/ s/\([1-9][0-9]*\),\([0-9][0-9][0-9][,.]\)/\1\2/; tb;' | awk -F '[^0-9.]' '{ for (i=NF; i>0; i--) if ($i ~ /^[0-9]+\.[0-9][0-9]$/) { print $i; next } }'

答案 2 :(得分:0)

这可能适合你(GNU sed):

 sed -r '/\n/!s/([^0-9]*\b(([0-9])[ ,]([0-9]{3})|([0-9]+))(\.[0-9]{2})\b)+/\n\3\4\5\6\n/;/^[0-9]+\.[0-9]{2}\b/P;D' file

这适用于所提供的数据,但有些规范有点粗略。