awk:将字符串解释为数学表达式

时间:2014-10-07 06:31:08

标签: shell awk

上下文:我有一个输入文件,其中包含带有关联值的参数,后跟文字数学表达式,例如:

PARAMETERS DEFINITION
A = 5; B = 2; C=1.5; D=7.5

MATHEMATICAL EXPRESSIONS
A*B
C/D
...

我希望将第二部分的字符串解释为数学表达式,以便在输出文件中得到表达式的结果:

...
MATHEMATICAL EXPRESSIONS
10
0.2
...

我已经做了什么:到目前为止,使用awk,我将所有参数名称及其对应的值存储在两个不同的数组中。然后我用它的值替换每个参数,这样我现在处于与this thread的作者类似的情况。 但是,他/她得到的答案不是awk,除了最后一个非常特定于她/他的情况,并且很难理解我作为awk和shell脚本的初学者。

我后来尝试了什么:由于我不知道如何在awk中执行此操作,我的想法是将新字段值存储在变量中,然后在awk中使用shell命令像这样的脚本:

#!bin/awk -f
BEGIN{}
{ 
myExpression=$1
system("echo $myExpression | bc")
}
END{}

遗憾的是,这不起作用,因为变量以某种方式不被echo命令识别。

我想要的是什么: 我更喜欢单独使用awk而不调用外部函数的解决方案,但是,如果它更简单,我不会使用shell命令。

编辑考虑到目前为止的所有评论,我会更精确,我的输入文件看起来更像是这样:

PARAMETERS_DEFINITION
[param1] = 5
[param2] = 2
[param3] = 1.5
[param4] = 7.5

MATHEMATICAL_EXPRESSIONS
[param1]*[param2]
some text containing also numbers and formulas that I do not want to be affected. 
e.g: 1.45*2.6 = x, de(x)/dx=e(x) ; blah,blah,blah
[param3]/[param4]

参数的名称足够复杂,因此文档中字符串:"[param#]"的任何匹配对应于我想要为其值更改的参数。

以下是我设置存储参数的方式,它们在数组中的值如下:

{   
if (match($2,/PARAMETERS_DEFINITION/) != 0) {paramSwitch = 1}
if (match($2,/MATHEMATICAL_EXPRESSIONS/) != 0) {paramSwitch = 0} 

if (paramSwitch == 1)
{
parameterName[numOfParam] = $1 ;  
parameterVal[numOfParam] = $3 ;     
numOfParam += 1
}
}

5 个答案:

答案 0 :(得分:1)

而不是:

{ 
  myExpression=$1
  system("echo $myExpression | bc")
}

我想你想要这个:

{ 
  myExpression=$1
  system("echo " myExpression " | bc")
}

那是因为在awk中,赋值不会最终成为环境变量,并且将字符串放在一起会将它们连接起来。

答案 1 :(得分:1)

你问 awk:将字符串解释为数学表达式 - 这个功能通常称为eval,而不,(AFAIK)awk不知道这样的功能。因此,您的问题是典型的XY problem

正确的工具是bc,您(几乎)不需要修改任何内容,只需输入bc输入,只确保变量是小写的,例如以下输入(编辑你的例子)

#PARAMETERS DEFINITION
a=5; b=2; c=1.5; d=7.5

#MATHEMATICAL EXPRESSIONS
a*b
c/d

使用like

bc -l < inputfile

产生

10
.20000000000000000000

修改

用于编辑,用于新输入数据。以下

grep '\[' inputfile | sed 's/[][]//g' | bc -l

输入

PARAMETERS_DEFINITION
[param1] = 5
[param2] = 2
[param3] = 1.5
[param4] = 7.5

MATHEMATICAL_EXPRESSIONS
[param1]*[param2]
some text containing also numbers and formulas that I do not want to be affected. 
e.g: 1.45*2.6 = x, de(x)/dx=e(x) ; blah,blah,blah
[param3]/[param4]

产生以下输出:

10
.20000000000000000000

e.g。只展示包含[的内容 - 任何参数定义或表达式,删除任何[],例如创建以下bc程序:

param1 = 5
param2 = 2
param3 = 1.5
param4 = 7.5
param1*param2
param3/param4

并发送整个&#34;程序&#34;到bc ...

答案 2 :(得分:0)

$ cat test
PARAMETERS DEFINITION
A=5; B=2; C=1.5; D=7.5

MATHEMATICAL EXPRESSIONS
A*B
C/D
$ awk -vRS='[= ;\n]' '{if ($0 ~ /[0-9]/){a[x] = $0; print x"="a[x]}else{x=$0}}/MATHEMATICAL/{print "MATHEMATICAL EXPRESSIONS"}{if ($0~"*") print a[substr($0,1,1)] * a[substr($0,3,1)]}{if ($0~"/") print a[substr($0,1,1)] / a[substr($0,3,1)]}' test
A=5
B=2
C=1.5
D=7.5

MATHEMATICAL EXPRESSIONS
10
0.2

格式良好:

$ cat test.awk
# Store all variables in an array
{ 
  if ($0 ~ /[0-9]/){
    a[x] = $0; 
    print x " = " a[x] # Print the keys & values
  }
  else{
   x = $0
  }
}
# Print header
/MATHEMATICAL/ {print "MATHEMATICAL EXPRESSIONS"}

# Do the maths (case can work too, but it's not as widely available)
{ 
  if ($0~"*") 
     print a[substr($0,1,1)] * a[substr($0,3,1)]
}

{
  if ($0~"/") 
     print a[substr($0,1,1)] / a[substr($0,3,1)]
}

{
  if ($0~"+") 
     print a[substr($0,1,1)] + a[substr($0,3,1)]
}

{
  if ($0~"-") 
     print a[substr($0,1,1)] - a[substr($0,3,1)]
}
$ cat test
PARAMETERS DEFINITION
A=5; B=2; C=1.5; D=7.5

MATHEMATICAL EXPRESSIONS
A*B
C/D
D+C
C-A

$ awk -f test.awk -vRS='[= ;\n]' test
A = 5
B = 2
C = 1.5
D = 7.5
MATHEMATICAL EXPRESSIONS
10
0.2
9
-3.5

答案 3 :(得分:0)

这有点类似于awk的eval,它在上下文需要时是一个神奇的转换,这里添加+0会进行转换。

我为您提供的内容(下面已详细说明),其中包含一个名为awkinput的文件,其中包含您的示例输入

awk '/[A-Z]=[0-9.]+;/ { for (i=1;i<=NF ;i++) { print "working on "$i; split($i,fields,"="); sub(/;/,"",fields[2]); params[fields[1]]=strtonum(fields[2]) } }; /[A-Z](*|\/|+|-)[A-Z]/ { for (p in params) { sub(p, params[p],$0); }; system("echo " $0 " | bc -ql") }' awkinput

的相关详细:

/[A-Z]=[0-9.]+;?/ { # if we match something like A=4.2 with or wothout a ; at end
  for (i=1;i<=NF ;i++) { # loop through the fields (separated by space, the default Field Separator of awk)
    print "working on "$i; # inform on what we do
    split($i,fields,"="); # split in an array to get param and value
    sub(/;/,"",fields[2]); # Eventually remove the ; at end
    params[fields[1]]=strtonum(fields[2]) # new array of parameters where the values are numeric
  }
}
 /[A-Z](*|\/|+|-)[A-Z]/ { #when the line match a math operation with one param on each side (at least)
  for (p in params) { # loop over know params
    sub(p, params[p],$0); # replace each param with its value
  }; 
   system("echo " $0 " | bc -ql") # print the result (no way to get of system call here)
}

缺点:

AB*C形式的数学运算将解析为52*1.5

答案 4 :(得分:0)

以BIDMAS为基础,我在awk中创建了这个数学函数 我还没有包括括号(或索引),因为它们需要一些额外的努力,但我可能会在以后添加它们 这个awk脚本可以像bc一样有效地工作 无需系统调用,全部都是awk。

所有应用程序的通用版本

awk '{split($0,a,"+")
            for(i in a){
            split(a[i],s,"-")
            for(j in s){
                    split(s[j],m,"*")
                    for(k in m){
                            split(m[k],d,"/")
                            for(l in d){
                                    if(l>1)d[1]=d[1]/d[l]
                            }
                            m[k]=d[1]
                            delete d
                            if(k>1)m[1]=m[1]*m[k]
                    }
                    s[j]=m[1]
                    delete m
                    if(j>1)s[1]=s[1]-s[j]
            }
            a[i]=s[1]
            delete s
    }
            for(i in a)b=b+a[i];print b}{b=0}' file

对于您的具体示例

awk '
/MATHEMATICAL_EXPRESSIONS/{z=1}
NR>1&&!z{split($0,y," = ");x[y[1]]=y[2]}

z&&/[\+\-\/\*]/{
    for (n in x)gsub(n,x[n])
    split($0,a,"+")
        for(i in a){
                split(a[i],s,"-")
                for(j in s){
                        split(s[j],m,"*")
                        for(k in m){
                                split(m[k],d,"/")
                                for(l in d){
                                        if(l>1)d[1]=d[1]/d[l]
                                }
                                m[k]=d[1]
                                delete d
                                if(k>1)m[1]=m[1]*m[k]
                        }
                        s[j]=m[1]
                        delete m
                        if(j>1)s[1]=s[1]-s[j]
                }
                a[i]=s[1]
                delete s
        }
                for(i in a)b=b+a[i];print b}{b=0}' file