# How can I evaluate a math equation, one per line in a file?

I have a file with the following:

37 * 60 + 55.52
34 * 60 + 51.75
36 * 60 + 2.88
36 * 60 + 14.94
36 * 60 + 18.82
36 * 60 + 8.37
37 * 60 + 48.71
36 * 60 + 34.17
37 * 60 + 42.52
37 * 60 + 51.55
35 * 60 + 34.76
34 * 60 + 18.90
33 * 60 + 49.63
34 * 60 + 37.73
36 * 60 + 4.49

I need to write a shell command or Bash script that, for each line in this file, evaluates the equation and prints the result. For example, for line one I expect to see 2275.52 printed. Each result should print once per line.

I've tried cat math.txt | xargs -n1 expr, but this doesn't work. It also seems like awk might be able to do this, but I'm unfamiliar with that command's syntax, so I don't know what it would be.

• I neglected to mention this but I'm using MinGW on Windows, and I don't have a bc command. I figured just basic shell utilities would get it done but I guess not? – void.pointer Nov 21 '19 at 18:48
• Do you have awk? – schrodigerscatcuriosity Nov 21 '19 at 19:21
• @void.pointer: bc is a "basic shell utility" – jesse_b Nov 21 '19 at 19:24
• @void.pointer MINGW should ship with bc because it claims to be POSIX compliant and POSIX requires the environment provide bc – slebetman Nov 22 '19 at 4:47

This awk seems to do the trick:

awk "BEGIN { print (\$i) }"
done < math.txt

From here

Note that we're using (\$i) instead of \$i to avoid problems with arithmetic expressions like 1 > 2 (print 1 > 2 would print 1 into a file called 2, while print (1 > 2) prints 0, the result of that arithmetic expression).

Note that since the expansion of the \$i shell variable ends up being interpreted as code by awk, that's essentially a code injection vulnerability. If you can't guarantee the file only contains valid arithmetic expressions, you'd want to put some input validation in place. For instance, if the file had a system("rm -rf ~") line, that could have dramatic consequences.

• +1 This is probably the only case where using a shell variable in awk directly is correct, rather that using the -v option. – user000001 Nov 22 '19 at 8:42

here is what I whould do not sure it is the best method

bc < toto

depending what you want to do with datas

francois@zaphod:~\$ cat > toto
37 * 60 + 55.52
34 * 60 + 51.75
36 * 60 + 2.88
36 * 60 + 14.94
36 * 60 + 18.82
36 * 60 + 8.37
37 * 60 + 48.71
36 * 60 + 34.17
37 * 60 + 42.52
37 * 60 + 51.55
35 * 60 + 34.76
34 * 60 + 18.90
33 * 60 + 49.63
34 * 60 + 37.73
36 * 60 + 4.49
francois@zaphod:~\$ while read ; do echo " \$REPLY" | bc  ; done < toto
2275.52
2091.75
2162.88
2174.94
2178.82
2168.37
2268.71
2194.17
2262.52
2271.55
2134.76
2058.90
2029.63
2077.73
2164.49
francois@zaphod:~\$

without BC command you cannot use decimal values :

francois@zaphod:~\$ while read ; do echo \$(( "REPLY" )) ; done < toto
-bash: 37 * 60 + 55.52: syntax error: invalid arithmetic operator (error token is ".52")
francois@zaphod:~\$

If you have perl:

perl -ne 'print eval \$_,"\n"' math.txt

(I get 50000 lines per second on my laptop using this).

• ok then : perl -pe 's/(.*)/eval \$1/e' file – francois P Nov 21 '19 at 19:26

With old-good Python:

\$ python -c \$'import sys;\nfor line in sys.stdin:print(eval(line))' <math.txt
2275.52
2091.75
2162.88
2174.94
2178.82
2168.37
2268.71
2194.17
2262.52
2271.55
2134.76
2058.9
2029.63
2077.73
2164.49

If you only need the results, I'd got with the answer provided by @francois-p

For fun and games, add paste and sed:

\$ paste <(sed 's/\(\$\)/\1\t=/g' somefile) <(bc < somefile)
37 * 60 + 55.52 =   2275.52
34 * 60 + 51.75 =   2091.75
36 * 60 + 2.88  =   2162.88
36 * 60 + 14.94 =   2174.94
36 * 60 + 18.82 =   2178.82
36 * 60 + 8.37  =   2168.37
37 * 60 + 48.71 =   2268.71
36 * 60 + 34.17 =   2194.17
37 * 60 + 42.52 =   2262.52
37 * 60 + 51.55 =   2271.55
35 * 60 + 34.76 =   2134.76
34 * 60 + 18.90 =   2058.90
33 * 60 + 49.63 =   2029.63
34 * 60 + 37.73 =   2077.73
36 * 60 + 4.49  =   2164.49

directly in bash/ksh (Edit: As it turns out, bash can't do this, only ksh - thanks for pointing this out):

> do
> echo \$((\$l))
> done <<!
> 37 * 60 + 55.52
> 34 * 60 + 51.75
> 36 * 60 + 2.88
> 36 * 60 + 14.94
> 36 * 60 + 18.82
> 36 * 60 + 8.37
> 37 * 60 + 48.71
> 36 * 60 + 34.17
> 37 * 60 + 42.52
> 37 * 60 + 51.55
> 35 * 60 + 34.76
> 34 * 60 + 18.90
> 33 * 60 + 49.63
> 34 * 60 + 37.73
> 36 * 60 + 4.49
> !
2275.52
2091.75
2162.88
2174.94
2178.82
2168.37
2268.71
2194.17
2262.52
2271.55
2134.76
2058.9
2029.63
2077.73
2164.49

This may require a fairly recent version of your shell - \$((...)) used to only do integer arithmetics.

• bash does not support floating point arithmetics. – FelixJN Nov 22 '19 at 12:40
• Yeah what shell are you using that can do this? – jesse_b Nov 22 '19 at 12:54
• Interesting - I have to admit, I hardly ever use bash, I prefer ksh, but that was for other reasons. – j4nd3r53n Nov 22 '19 at 12:59

With Perl:

perl -ple '\$_=eval' ex
perl -nE 'say eval' ex

with Python:

python3 -qi < ex
python3 -qic 'import sys; sys.ps1=""' < ex

ghci < ex
ghci < ex | grep -Po '> \S+\$'

With calc:

calc -f ex      # apt install apcalc if necessary

Using awk/python:

python -c "\$(awk '{printf "print %s;", \$0}' math.txt)"

awk is being used here to format your file into an input that python will accept, then python is doing the work.

Alternatively perl can be used in pretty much the same way:

perl -le "\$(awk '{printf "print %s;", \$0}' math.txt)"

Directly with awk:

awk '{ printf "%f\n", \$0 }' math.txt

The \$0 represents the entire line that is read line by line from the file.

Additionally, it is not susceptible to nasty injections. It will only evaluate a line as a floating point number.

Just feed the mess to bc -l, it will return one line result for each line of expression. (The -l loads the math library, it also sets scale to a higher value and so gives fractional results.)