首页 > 学技术 > 技术网文 > Linux Shell > 正文

[原创] [原创] 表达式求值程序 by AWK


来源 chinaunix.net 酷勤网整理

今天看到论坛中有人问:[url=http://bbs.chinaunix.net/viewthread.php?tid=737798&extra=page%3D2] awk如何计算命令行的输入? 
仔细想了想用awk确实不好做。因为awk中没有像其它语言
中的eval函数,于是自己试着编了一个eval函数,用来进
行表达式求值。
   用stack对运算符号及数值进行处理是这个脚本的关键。
由于时间有限,我只实现了+-*/%五种运算。代码写的比较
粗糙,欢迎大家批评指正。


用法(表达式必须以"@"结束):


echo "(3-1)*2.3-8.5%3-(9.12+2*3)/2@" | awk -f eval
echo 1+2+3*2 @| awk -f eval
awk -f eval <( seq 1 100|tr "\n" "+"|sed 's/+/@/100' )



代码:


#! /usr/bin/awk
# simple expression evaluator awk version
# author : dbcat at chinaunix.net
# email: deeperbluecat@gmail.com
# usage: example :
# echo "(3-1)*2.3-8.5%3-(9.12+2*3)/2@" | awk -f eval
# echo 1+2+3*2 @| awk -f eval
# awk -f eval <( seq 1 100|tr "\n" "+"|sed 's/+/@/100' )
# expression must be end with "@"
# have fun




BEGIN{

# 运算符集合
symbol="+-*%/()@";


# 构造运算符优先级关系
oppr["++"]=">";oppr["+-"]=">";oppr["+*"]="<";oppr["+/"]="<";oppr["+%"]="<";oppr["+("]="<"; oppr["+)"]=">";oppr["+@"]=">";
oppr["-+"]=">";oppr["--"]=">";oppr["-*"]="<";oppr["-/"]="<";oppr["-%"]="<";oppr["-("]="<"; oppr["-)"]=">";oppr["-@"]=">";
oppr["*+"]=">";oppr["*-"]=">";oppr["**"]=">";oppr["*/"]=">";oppr["*%"]="<";oppr["*("]="<"; oppr["*)"]=">";oppr["*@"]=">";
oppr["/+"]=">";oppr["/-"]=">";oppr["/*"]=">";oppr["//"]=">";oppr["/%"]="<";oppr["/("]="<"; oppr["/)"]=">";oppr["/@"]=">";
oppr["%+"]=">";oppr["%-"]=">";oppr["%*"]=">";oppr["%/"]=">";oppr["%%"]=">";oppr["%("]="<"; oppr["%)"]=">";oppr["%@"]=">";
oppr["(+"]="<";oppr["(-"]="<";oppr["(*"]="<";oppr["(/"]="<";oppr["(%"]="<";oppr["(("]="<"; oppr["()"]="=";
oppr[")+"]=">";oppr[")-"]=">";oppr[")*"]=">";oppr[")/"]=">";oppr[")%"]=">";                oppr["))"]=">";oppr[")@"]=">";
oppr["@+"]="<";oppr["@-"]="<";oppr["@*"]="<";oppr["@/"]="<";oppr["@("]="<";oppr["@%"]="<";                oppr["@@"]="=";


}



{
   gsub(/[-*%+()/@]/," & ",$0);
   expression=$0;
}



END{
 # 分解表达式
 split(expression,Ex," ");
 
 print evaluate(Ex);

}






 # 表达式求值函数 

function evaluate(Ex,OP,OF,c,k,t,x,str,y,r)
 {
    k=1;
    push(OP,"@");
    c=Ex[k];
    while(c!="@" || gettop(OP)!="@")
      {
        if(index(symbol,c)==0)
          {          
            push(OF,c);
            k=k+1;
            c=Ex[k];
            
          }else
          {
            t=priority(oppr,gettop(OP),c);
            if(t=="<")
             {
               push(OP,c);
               k=k+1;
               c=Ex[k];
               
             }else if(t=="=")
             {

               pop(OP);
               k=k+1;
               c=Ex[k];
             }else{
               
               str=pop(OP);
               y=pop(OF);
               x=pop(OF);
               r=calculate(x,str,y);
               push(OF,calculate(x,str,y));
     
            }
        
          }
 
      }
  return gettop(OF);
 }


# 二元运算函数

function calculate(x,st,y)
 {
    if(st=="+")
      return x+y;
    else if(st=="-")
      return x-y;
    else if(st=="*")
      return x*y;
    else if(st=="%")
      return x%y;
    else if(st=="/")
      return x/y;
 }


 # 优先级别比较

function priority(oppr,op1,op2)
 {
   return oppr[op1""op2];
 }


  # 取栈顶 

function gettop(arr,k,t)
 {
    k=0;
    for(t in arr)
     {
        k++;
     }
    return arr[k-1];
 }

 #  进栈函数

function push(arr,x,t,k)
{
  k=0;
  for(t in arr)
   {
      k++;
   }
 arr[k]=x;

}

 #  出栈函数

function pop(arr,va,t,u)

  u=0;
  for(t in arr)
    {
      u++;
    }
  va=arr[u-1]; 
  delete arr[u-1];
  return va;
}






 xlink 回复于:2006-04-13 21:01:52

dbcat你真厉害!

我也问了一个问题:
我知道怎么逐行读取一个文件,但怎么逐字读取一个文件?

帮帮我。


 dbcat 回复于:2006-04-13 21:06:53

你想干虾米?


 xlink 回复于:2006-04-13 21:11:25

引用:原帖由 dbcat 于 2006-4-13 21:06 发表
你想干虾米? 


我想知道方法。暂时还没想到有什么用,是一个字符一个字符地去读,就是相当于:
cat 1.txt>2.txt的功能。

[ 本帖最后由 xlink 于 2006-4-13 21:14 编辑 ]


 dbcat 回复于:2006-04-13 21:14:37

我猜用dd可以


 xlink 回复于:2006-04-13 21:24:54

引用:原帖由 dbcat 于 2006-4-13 21:14 发表
我猜用dd可以 


dd好象是用来cp文件的,我不是想去cp一个文件。
如果把一个文件全部读进一组变量里,你想想,多好的事情。


 无奈何 回复于:2006-04-13 22:21:07

谢谢 dbcat 费心写出的这个函数,我在windows下只有 GAWK 能够通过, awk95、nawk、mawk 都不能通过。
你的代码我现在还看不懂,看来我自己是不能通过简单修改来满足最初的需求了。先收藏起来,努力学习吧。


 johnny_jiang 回复于:2006-04-13 23:02:14

引用:原帖由 xlink 于 2006-4-13 21:24 发表

dd好象是用来cp文件的,我不是想去cp一个文件。
如果把一个文件全部读进一组变量里,你想想,多好的事情。 



dd是可以的,小弟试了一下,不过又缺陷

#  while char=`dd bs=1 count=1 2>/dev/null`; do echo $char; done <file

由于小弟学艺不精,导致当dd读完了整个文件后,还在继续读,望高手指点

用awk实现了一下,请试试

# awk 'BEGIN { FS="" } { for (i=1;i<=NF;i++) print $i }' file



 寂寞烈火 回复于:2006-04-14 11:19:44

dbcatMM is A awk's master ~ ^_^


 苏蓉蓉 回复于:2006-04-14 11:42:00

如果把乘阶运算符“^” , 和"sin cos exp ...." 加进去就好了


 r2007 回复于:2006-04-14 11:50:00

功能加多了的话,一不小心一个新的脚本语言就此诞生了


 dbcat 回复于:2006-04-14 13:47:44

呵呵,如果加sin log...的话,用一棵expression binary tree 来分析比较好。


[url=http://bbs.chinaunix.net/viewthread.php?tid=744577&extra=page%3D2]更新链接


#! /usr/bin/awk

# expression evaluator awk version
# author : dbcat at chinaunix
# email: deeperbluecat@gmail.com
# 用法:
# echo "4*arctan(sin(1))+sqrt(1.2)*(1-23*exp(3))-log(2+cos(3))"| awk -f eval
# echo "PI+E^sin(32.1%3.1)"|awk -f eval
# awk -f eval <( seq 1 100|tr "\n" "+"|sed 's/+//100' )
# echo '1!=2&1|0'|awk -f eval
# echo '1<=2+3>=(!2-1.1)+sin(1.2*(PI!=E))' | awk -f eval
# 支持的算术运算符号 : + - * / % ^ ( )
# 支持的函数运算 : sin cos tg log exp arctan sqrt int
# 支持的逻辑运算符号 : < > ! != <= >= & |
# 内置的常数 : PI  E
# 运行环境 : GNU Awk 3.1.4
# have fun






BEGIN{

# 运算符集合
symbol="+-*%<=!=>==|&>^SCLAEQTI/()@";
# 构造运算符优先级关系
consop(oppr);

}







#主函数

{
   split(funclib($0"@"),Ex," ");
   print evaluate(Ex);
}










# 表达式求值函数

function evaluate(Ex,OP,OF,c,k,t,x,str,y,r)
{
    k=1;
    push(OP,"@");
    c=Ex[k];
    while(c!="@" || gettop(OP)!="@")
      {
        if(index(symbol,c)==0)
          {
            push(OF,c);
            k=k+1;
            c=Ex[k];

          }else
          {
            t=priority(oppr,gettop(OP),c);
            if(t=="<")
             {
               push(OP,c);
               k=k+1;
               c=Ex[k];

             }else if(t=="=")
             {

               pop(OP);
               k=k+1;
               c=Ex[k];
             }else{

               str=pop(OP);
               y=pop(OF);
               x=pop(OF);
               r=calculate(x,str,y);
               push(OF,calculate(x,str,y));

            }

          }

      }
  return gettop(OF);
}


# 二元运算函数

function calculate(x,st,y)
{
    if(st=="+")
      return x+y;
    else if(st=="-")
      return x-y;
    else if(st=="*")
      return x*y;
    else if(st=="%")
      return x%y;
    else if(st=="/")
      return x/y;
    else if(st=="^")
      return x^y;
    else if(st=="S")
      return sin(y);
    else if(st=="C")
      return cos(y);
    else if(st=="L")
      return log(y);
    else if(st=="E")
      return exp(y);
    else if(st=="Q")
      return sqrt(y);
    else if(st=="A")
      return atan2(y,1);
    else if(st=="T")
      return sin(y)/cos(y);
    else if(st=="I")
      return int(y);
    else if(st=="<")
      return x<y;
    else if(st==">")
      return x>y;
    else if(st=="<=")
      return x<=y;
    else if(st==">=")
      return x>=y;
    else if(st=="==")
      return x==y;
    else if(st=="!=")
      return x!=y;
    else if(st=="&")
      return x&&y;
    else if(st=="|")
      return x||y;
    else if(st=="!")
      return !y;

}




# 算符优先关系函数
function consop(oppr,t,x,y,z,i,k,j,xl,yl,zl,tl)
{
   xl="+-";
   yl=" * / % ^ < <= ! & | > >= == != ";
   zl="SCALEQTI";
   tl="+ - * / % ^ < > ! != == >= <= & | S C A L E Q T I";
   split(tl,t," ");
   split(xl,x,"");
   split(yl,y," ");
   split(zl,z,"");

   for(k=1;k<=length(xl);k++)
     {
       for(j=1;j<=length(xl);j++)
        {
          oppr[x[k]""x[j]]=">";
          oppr[x[j]""x[j]]=">";
        }
       for(j=1;j<=length(yl);j++)
        {
          oppr[x[k]""y[j]]="<";
          oppr[y[j]""x[k]]=">";
          for(i=1;i<=length(yl);i++)
            {
              oppr[y[j]""y]=">";
              oppr[y""y[j]]=">";
            }
          for(i=1;i<=length(zl);i++)
            {
                oppr[y[j]""z]="<";
                oppr[z""y[j]]=">";
            }
        }
      for(j=1;j<=length(zl);j++)
        {
          oppr[x[k]""z[j]]="<";
          oppr[z[j]""x[k]]=">";
          for(i=1;i<=length(zl);i++)
           {
              oppr[z[j]""z]=">";
              oppr[z""z[j]]=">";
           }
        }
     }



       for(j=1;j<=length(tl);j++)
        {
           oppr[t[j]"("]="<";
           oppr[t[j]")"]=">"
           oppr[t[j]"@"]=">";
           oppr[")"t[j]]=">";
           oppr["("t[j]]="<";
           oppr["@"t[j]]="<";
        }
      for(j=1;j<=length(yl);j++)
       {
           oppr[y[j]"^"]="<";
       }

   oppr["(("]="<";oppr["()"]="=";
   oppr["))"]=">";oppr[")@"]=">";
   oppr["@("]="<";oppr["@@"]="=";


}






# 优先级别比较

function priority(oppr,op1,op2)
{
    return oppr[op1""op2];
}






# 分解表达式

function funclib(expr)
{

   gsub(/PI/,"3.141592653589793238460",expr);
   gsub(/E/,"2.718281828459045235360",expr);
   gsub(/sin/," & S ",expr);
   gsub(/cos/," & C ",expr);
   gsub(/log/," & L ",expr);
   gsub(/exp/," & E ",expr);
   gsub(/arctan/," & A ",expr);
   gsub(/sqrt/," & Q ",expr);
   gsub(/tg/," & T ",expr);
   gsub(/int/," & I ",expr);
   gsub(/[-*%+>=!&|<^()SCLAEQTI/@]/," & ",expr);
   gsub(/<[ ]*=/,"<=",expr);
   gsub(/>[ ]*=/,">=",expr);
   gsub(/=[ ]*=/,"==",expr);
   gsub(/![ ]*=/,"!=",expr);
   gsub(/&/," & ",expr);
   gsub(/[|]/," & ",expr);
   gsub(/![^=]/," N ! ",expr);

   return expr;

}




# 取栈顶

function gettop(arr,k,t)
{
    k=0;
    for(t in arr)
     {
        k++;
     }
    return arr[k-1];
}

#  进栈函数

function push(arr,x,t,k)
{
  k=0;
  for(t in arr)
   {
      k++;
   }
arr[k]=x;

}


#  出栈函数

function pop(arr,va,t,u)
{
  u=0;
  for(t in arr)
    {
      u++;
    }
  va=arr[u-1];
  delete arr[u-1];
  return va;
}


[ 本帖最后由 dbcat 于 2006-4-25 11:08 编辑 ]


 waker 回复于:2006-04-14 16:03:00

来个马后炮

[waker@proxy ~]$ echo "1+2*3
4/6
2^3+5*6"|
awk '{a=$0;cmd="awk \047BEGIN{print "$0" }\047";cmd|getline ;print a"="$0}'

1+2*3=7
4/6=0.666667
2^3+5*6=38



 waker 回复于:2006-04-14 16:06:59

忘记及时回收垃圾了

awk '{a=$0;cmd="awk \047BEGIN{print "$0" }\047";cmd|getline ;close(cmd);print a"="$0}'



 dbcat 回复于:2006-04-14 16:51:20

waker真棒!
用getline实现awk自身调用,这应该是最简单的办法了!


 xstart 回复于:2006-04-18 19:00:47

复杂的程序,我觉得用C/C++实现更好,用python或者php(CLI模式)也可以,如果你喜欢perl还可以用perl,不过我不太喜欢。毕竟shell,awk都不是一个全功能的语言,解决复杂问题比较费劲


 likfan 回复于:2006-04-19 11:57:59

真是精华啊。




原文链接:http://bbs.chinaunix.net/viewthread.php?tid=738738
转载请注明作者名及原文出处



收藏本页到: