7

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 at 18:48
  • 1
    Do you have awk? – guillermo chamorro Nov 21 at 19:21
  • 3
    @void.pointer: bc is a "basic shell utility" – Jesse_b Nov 21 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 at 4:47
12

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.

  • 3
    +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 at 8:42
8

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

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

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
1

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.

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

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.