8

I'm simply trying to multiplying some float variables using bc:

#!/bin/bash

a=2.77 | bc
b=2.0  | bc

for cc in $(seq 0. 0.001 0.02)
do
    c=${cc} | bc
    d=$((a * b * c)) | bc
    echo "$d" | bc
done

And this does not give me an output. I know it's a silly one but I've tried a number of combinations of bc (piping it in different places etc.) to no avail.

Any help would be greatly appreciated!

Peter O.
  • 32,158
  • 14
  • 82
  • 96
Omkar Myatra
  • 115
  • 2
  • 2
  • 5
  • 4
    Each time you `| bc` you are running a *new* `bc` process. It doesn't know anything about the previous `bc` processes. – Etan Reisner Jun 24 '15 at 18:46
  • 2
    `a=2.77 | bc` is also meaningless (I'm surprised it isn't a syntax error actually) since `a=2.77` is an assignment that creates no output for `bc` to read and operate on. You would need `echo` there like you have on the `echo "$d" | bc` line. – Etan Reisner Jun 24 '15 at 18:47
  • 4
    @EtanReisner: `a=2.77|bc` starts two subshells, attaching stdout of the first to stdin of the second through a pipe. In the first, it sets `a` to 2.77 and terminates, closing stdout. In the second, reading from stdin produces an (almost) immediate EOF, so `bc` does nothing. The result is a very expensive no-op; not even the assignment happens in the executing shell environment. But it's certainly not a syntax error: "A 'simple command' is a sequence of optional variable assignments and redirections, in any sequence, *optionally* followed by words and redirections" (XCU 2.9.1) – rici Jun 24 '15 at 19:47
  • @rici Yeah, I understand what happens internally (in terms of what gets executed). I was mostly just surprised that the shell didn't notice the lack of command early and choose to bail (since that's a different case of command than one with a command) but that's certainly extra processing at the initial pass that is not at all necessary at that point. – Etan Reisner Jun 24 '15 at 19:53
  • 1
    @EtanReisner: I don't think it could bail without violating posix. There is no requirement that a command have a command name (as per my quote) and a pipeline is just a series of commands separated by pipes. So it's a valid pipeline. (I included the precise mechanism for whoever else might read the comment. I know you understand it :) ) – rici Jun 24 '15 at 19:54
  • the last part of the answer was exactly what I wanted to do! Thanks a lot! :) – Omkar Myatra Jun 26 '15 at 20:18

2 Answers2

13

bc is a command-line utility, not some obscure part of shell syntax. The utility reads mathematical expressions from its standard input and prints values to its standard output. Since it is not part of the shell, it has no access to shell variables.

The shell pipe operator (|) connects the standard output of one shell command to the standard input of another shell command. For example, you could send an expression to bc by using the echo utility on the left-hand side of a pipe:

echo 2+2 | bc

This will print 4, since there is no more here than meets the eye.

So I suppose you wanted to do this:

a=2.77
b=2.0
for c in $(seq 0. 0.001 0.02); do
  echo "$a * $b * $c" | bc
done

Note: The expansion of the shell variables is happening when the shell processes the argument to echo, as you could verify by leaving off the bc:

a=2.77
b=2.0
for c in $(seq 0. 0.001 0.02); do
  echo -n "$a * $b * $c" =
  echo "$a * $b * $c" | bc
done

So bc just sees numbers.

If you wanted to save the output of bc in a variable instead of sending it to standard output (i.e. the console), you could do so with normal command substitution syntax:

a=2.77
b=2.0
for c in $(seq 0. 0.001 0.02); do
  d=$(echo "$a * $b * $c" | bc)
  echo "$d"
done
rici
  • 234,347
  • 28
  • 237
  • 341
2

To multiply two numbers directly, you would do something like:

echo 2.77 * 2.0 | bc

It will produce a result to 2 places - the largest number of places of the factors. To get it to a larger number of places, like 5, would require:

echo "scale = 5; 2.77 * 2.0" | bc

This becomes more important if you're multiplying numerals that each have a large number of decimal places.

As stated in other replies, bc is not a part of bash, but is a command run by bash. So, you're actually sending input directly to the command - which is why you need echo. If you put it in a file (named, say, "a") then you'd run "bc < a". Or, you can put the input directly in the shell script and have a command run the designated segment as its input; like this:

cat <<EOF
Input
EOF

... with qualifiers (e.g. you need to write "" as "\", for instance).

Control flow constructs may be more problematic to run in BC off the command line. I tried the following

echo "scale = 6; a = 2.77; b = 2.0; define f(cc) { auto c, d; c = cc; d = a*b*c; return d; } f(0); f(0.001); f(0.02)" | bc

and got a syntax error (I have a version of GNU-BC installed). On the other hand, it will run fine with C-BC

echo "scale = 6; a = 2.77; b = 2.0; define f(cc) { auto c, d; c = cc; d = a * b * c; return d; } f(0); f(0.001); f(0.02)" | cbc

and give you the expected result - matching the example you cited ... listing numbers to 6 places.

C-BC is here (it's operationally a large superset of GNU-BC and UNIX BC, but not 100% POSIX compliant):

https://github.com/RockBrentwood/CBC

The syntax is closer to C, so you could also write it as

echo "scale = 6, a = 2.77, b = 2.0; define f(cc) { return a * b * cc; } f(0); f(0.001); f(0.02)" | cbc

to get the same result. So, as another example, this

echo "scale = 100; for (x = 0, y = 1; x < 50; y *= ++x); y" | cbc

will give you 50 factorial. However, comma-expressions, like (x = 0, y = 1) are not mandated for bc by POSIX, so it will not run in other bc dialects, like GNU BC.

Anand Vidvat
  • 977
  • 7
  • 20
NinjaDarth
  • 287
  • 1
  • 3