Bash bc returns (standard_in) 1: parse error only when part of for loop

3.7k views Asked by At

I'm iterating through an array of integers ${startTimes} (marker locations in an audio file, in samples) and using bc to convert those integers to milliseconds. I'm passing the results into a new array ${msValuesArray}. If I run each array element one at a time it works fine. If I run it in a for loop:

for i in $(seq 0 ${#startTimes[@]}); do 
    msValuesArray+=($(bc <<< ${startTimes[i]}/44.1))
done

The resulting ${msValuesArray} contains the expected results, but the terminal outputs (standard_in) 1: parse error.

While I intend to use this in a shell script, and after reading other questions here I learned that adding #!/bin/bash to the beginning of the command avoids the parse error, I still don't understand the following:

a) why does the manual passing of a ${startTimes} element into bc work without the parse error while the for loop also works, yet outputs the parse error (outside of the shell script)?

b) despite the parse error, I have the resulting array that I want. Should I ignore the error?

c) when adding #!/bin/bash to the beginning of the commands (still outside of the shell script, just in the command line) why are the results inaccessible? (Entering echo ${msValuesArray[@]} returns an empty array.)

d) While running inside the shell script, is the same error happening but just not printing to the terminal?

Any help is appreciated. Thanks.

2

There are 2 answers

4
Benjamin W. On BEST ANSWER

You can iterate over the array directly instead of going via indices:

for t in "${startTimes[@]}"; do
    msValuesArray+=($(bc <<< "$t / 44.1"))
done

This makes the loop easier to read.

You get a parse error because you're trying to access a non-existing element (see John1024's answer), so bc sees just / 44.1. You shouldn't ignore the error.

You should quote your here-string, even though in this very instance it doesn't seem to cause problems1.

If you enter #!/bin/bash just on the command line, it has no effect at all, it's just considered a comment. It only does something as the first line of a script, namely indicate what interpreter should be used. If, as indicated by your comment, you enter the whole thing on a single line as

#!/bin/bash; for ... (etc) ...

nothing at all happens. It's just a comment.

Lastly, you're truncating your results. If you want them more precise, you can set scale to a sensible value, as in

bc <<< "scale = 3; $t / 44.1"

1 Problems such as (unwanted) word splitting and globbing. This article is a good overview about the how and why of quoting.

1
John1024 On

You have an off-by-1 problem. Observe that, with your sample startTimes, seq generates 10 numbers:

$ startTimes=(0 87053 91463 190062 194472 290520 294930 387582 391992)
$ seq 0 ${#startTimes[@]}
0
1
2
3
4
5
6
7
8
9

The problem is that startTimes has only 9 entries:

$ declare -p startTimes
declare -a startTimes=([0]="0" [1]="87053" [2]="91463" [3]="190062" [4]="194472" [5]="290520" [6]="294930" [7]="387582" [8]="391992")

When i=9, startTimes[9] evaluates to an empty string and that leads to the bc error that you see:

$ i=9; msValuesArray+=($(bc <<< ${startTimes[i]}/44.1))
(standard_in) 1: syntax error

Or, more directly:

$ bc <<<"/44.1"
(standard_in) 1: syntax error