【问题标题】:awk: Interpreting strings as mathematical expressionsawk:将字符串解释为数学表达式
【发布时间】:2014-10-08 00:08:36
【问题描述】:

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

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 命令的解决方案。

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

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
}
}

【问题讨论】:

  • 如何将参数名称存储在数组中?还有只有一行参数定义吗?
  • @Jidder :我将从您的第二个问题开始:不,实际上,每行有一个参数,更像这样:A = 5 ...,等号周围有空格。我利用这一点将适当的字段存储在 中的 for 循环递增 直到遇到 行。够清楚吗?
  • 能否在您的问题中举例说明一下?
  • “一些文本还包含我不想受到影响的数字和公式。”非常无益。发布真实输入
  • @Jidder:我编辑了帖子以包含您需要的内容

标签: shell awk


【解决方案1】:

而不是这个:

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

我想你会想要这个:

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

这是因为在 awk 中,赋值最终不会成为环境变量,而将字符串彼此相邻放置会将它们连接起来。

【讨论】:

  • 这确实很有帮助,非常感谢。如果没有人提出仅基于 awk 的解决方案,我会相信你的答案。
【解决方案2】:

您问 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

使用喜欢

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

例如仅找出包含 [ 的行 - 任何参数定义或表达式,删除任何 [],例如创建以下bc 程序:

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

并将整个“程序”发送到bc...

【讨论】:

  • 我认为你错了,因为我从问题本身开始就陷入了 XY 问题陷阱。之后我才解释了我是如何解决它的。
  • @Mary 在您编辑问题之前,我先写一下 XY。简而言之,如果您提出错误(未完全定义)的问题,则会得到错误的答案。 ;) 无论如何,我仍然确定:1.) 将您的输入转换为 BC 程序 2.) 用 BC 计算表达式(作为一个整体 - 不是逐行)是最简单的方法...... - 见编辑。
【解决方案3】:

使用 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

【讨论】:

  • 如果使用(sqrt(e^3.543)%3i)/4.5(43)会怎样?
  • @confused00 我在开始时明确声明我没有包含括号,我还打算添加我还没有添加索引。无需发布荒谬的方程式,因为您对我的评论不满意。
  • 重点是“要走多远以及要做出什么假设”是一条任意线,我只是提出了适合 OP 的输入和解释的解决方案
  • @Mary 是的,这将适用于您想要的每个运算符(例如索引),除了括号。括号需要稍微不同的方法。我正在考虑使用匹配语句并使函数递归。不过我现在没有时间,但我稍后会尝试添加这个
  • @confused00 要点是,您的答案对问题是如此具体,并且将所有内容都作为文字输入,以至于除了 OP 并且仅适用于这种特定情况,它对任何人都没有用,并查看评论你的答案,它甚至不适合那个。
【解决方案4】:

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

我为你得到了什么(下面的详细版本),带有一个名为 awkinput 的文件和你的示例输入

awk '/[A-Z]=[0-9.]+;/ { for (i=1;i&lt;=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

【讨论】:

  • 谢谢你,第一部分(参数的记忆)完全不同,你的方式看起来比我的更健壮。对于第二部分(用值替换名称),它看起来很像我所做的。
  • 确实,这只是对您的问题的帮助,并提供了一些帮助实施的细节。听起来你在这里用错误的工具刮胡子
【解决方案5】:
$ 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

【讨论】:

  • 如果它使用两个运算符怎么办? A+B-C
  • 感谢这个优雅的解决方案,它鼓舞人心。但是,它不够通用,不适用于我的情况(请参阅对初始问题的编辑)。它还提出了吉德提到的缺点。不过还是谢谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-11
  • 2011-01-17
  • 1970-01-01
相关资源
最近更新 更多