5

Is there a way to evaluate a string as a math expression in awk?

balter@spectre3:~$ echo "sin(0.3) 0.3" | awk '{print $1,sin($2)}'
sin(0.3) 0.29552

I would like to know a way to also have the first input evaluated to 0.29552.

abalter
  • 9,663
  • 17
  • 90
  • 145
  • How about using this `echo "sin(0.3) 0.3" | awk '{match($1,/\(.*\)/);print sin(substr($1,RSTART+1,RLENGTH-1)),sin($2)}'` :) I know you want to pass it as a MATH expression(which I am not aware as of now, looking to documentations), thought a funny way to play around with this one. – RavinderSingh13 Jan 12 '19 at 04:38
  • 1
    Well, there is the extended version of `calc3` awk script by Kenny McCormack and Alan Linton floating around the internets, here is one link: https://groups.google.com/forum/#!topic/comp.lang.awk/C4SbutsY1JA The original `calc3` only supported basic operations but the linked one seems to handle sin and such. – James Brown Jan 12 '19 at 08:53
  • `echo "sin(0.3)" | awk -f calc3.awk` produces `sin(0.3) = 0.29552` – James Brown Jan 12 '19 at 09:02
  • ````echo "sin(0.3) 0.3" | perl -ne ' /(\S+)\s+(\S+)/ and print eval($1), " ", $2 '```` would also work.. – stack0114106 Jan 14 '19 at 16:41

5 Answers5

6

You can just create your own eval function which calls awk again to execute whatever command you want it to:

$ cat tst.awk
{ print eval($1), sin($2) }

function eval(str,      cmd,line,ret) {
    cmd = "awk \047BEGIN{print " str "; exit}\047"
    if ( (cmd | getline line) > 0 ) {
        ret = line
    }
    close(cmd)
    return ret
}

$ echo 'sin(0.3) 0.3' | awk -f tst.awk
0.29552 0.29552

$ echo '4*7 0.3' | awk -f tst.awk
28 0.29552

$ echo 'tolower("FOO") 0.3' | awk -f tst.awk
foo 0.29552
Ed Morton
  • 188,023
  • 17
  • 78
  • 185
5

Here's a simple one liner!

math(){ awk "BEGIN{printf $1}"; }

Examples of use:

math 1+1  

Yields "2"

math 'sqrt(25)'

Yeilds "5"

x=100; y=5; math "sqrt($x) + $y"

Yeilds "15"

BuvinJ
  • 10,221
  • 5
  • 83
  • 96
4

awk lacks an eval(...) function. This means that you cannot do string to code translation based on input after the awk program initializes. Ok, perhaps it could be done, but not without writing your own parsing and evaluation engine in awk.

I would recommend using bc for this effort, like

[edwbuck@phoenix ~]$ echo "s(0.3)" | bc -l
.29552020666133957510

Note that this would require sin to be shortened to s as that's the bc sine operation.

Edwin Buck
  • 69,361
  • 7
  • 100
  • 138
  • 2
    Why isn't there a sane command line math library for linux? My current solution is `m(){ python -c "import math; print($1);}`. I could use perl as well. My previous boss saw this and told me I had just built the worlds most expensive calculator. – abalter Jan 12 '19 at 07:13
  • 1
    @abalter I was thinking that `bc` was for `basic calculator` and satisfied the "sane command line math" need; but, there is no shortage of ways to do Math in Linux. I agree that `s(..)` and `c(...)` are odd ways of writing `sin` and `cos`, it's a shame they didn't opt for `sin(...)` and `cos()`. Also, one could almost use bash's `echo $(($expr))` approach, except that it doesn't support trig. – Edwin Buck Jan 12 '19 at 07:20
  • @EdwinBuck Interestingly POSIX limits function and variable names in bc to be a single letter. (While multi letter implementations are available) – hek2mgl Jan 12 '19 at 09:03
  • 1
    @abalter wrt `Why isn't there a sane command line math library for linux?` - because that's not what a shell is for. A shell is an environment from which to call tools and has a small language to help sequence those calls. It's not intended/designed to be a full featured programming language and if you're trying to use it as such then you're missing the point. – Ed Morton Jan 12 '19 at 17:54
2

With gawk version 4.1.2 :

echo "sin(0.3) 0.3" | awk '{split($1,a,/[()]/);f=a[1];print @f(a[2]),sin($2)}'

It's ok with tolower(FOO) too.

ctac_
  • 2,413
  • 2
  • 7
  • 17
  • Good idea for function calls that take single numeric or string arguments (you just couldn't use it with `4*7` or `index("abc","b")` or `sin(0.1+0.2)` for example). btw `[(]|[)]` = `[()]`. – Ed Morton Jan 13 '19 at 14:37
0

You can try Perl as it has eval() function.

$  echo "sin(0.3)" | perl -ne ' print eval '
0.29552020666134
$

For the given input,

$ echo "sin(0.3) 0.3" | perl -ne ' /(\S+)\s+(\S+)/ and print eval($1), " ", $2 '
0.29552020666134 0.3
$
stack0114106
  • 8,534
  • 3
  • 13
  • 38