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.

10 Answers10

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.

  • 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 '19 at 08: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:~$
francois P
  • 1,219
7

If you have perl:

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

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

Ole Tange
  • 35,514
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

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.

j4nd3r53n
  • 715
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
markgraf
  • 2,860
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
JJoao
  • 12,170
  • 1
  • 23
  • 45
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)"
jesse_b
  • 37,005
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.)

vonbrand
  • 18,253