9

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.

7
  • 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? Nov 21, 2019 at 18:48
  • 1
    Do you have awk? Nov 21, 2019 at 19:21
  • 3
    @void.pointer: bc is a "basic shell utility"
    – jesse_b
    Nov 21, 2019 at 19:24
  • 3
    @void.pointer MINGW should ship with bc because it claims to be POSIX compliant and POSIX requires the environment provide bc
    – slebetman
    Nov 22, 2019 at 4:47

10 Answers 10

13

This awk seems to do the trick:

while IFS= read i; do 
  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
  • 4
    +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, 2019 at 8:42
9

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:~$
0
7

If you have perl:

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

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

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

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
3

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
3

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

$ while read l
> 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.

3
  • bash does not support floating point arithmetics.
    – FelixJN
    Nov 22, 2019 at 12:40
  • Yeah what shell are you using that can do this?
    – jesse_b
    Nov 22, 2019 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, 2019 at 12:59
3

With Perl:

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

with Python:

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

With Haskell:

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

With calc:

calc -f ex      # apt install apcalc if necessary
2

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)"
0
2

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.

0

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.)

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .