Can't seem to add two numbers in shell - bash

I have been googling and trying different methods but nothing seems to work.
I have the following code
string=0 4 5 27 8 7 0 6
total=0
for n in "$string"; do
total=$(($total + $n))
done
This way I want to count the total sum of all the numbers within that string.
I have also tried expr "$total" + "$n" but that gives me an error saying the operand is not an integer.
Any suggestion how I might make this work?

Don't quote the string in the in clause, quoted string is not split into words:
#! /bin/bash
total=0
string='0 4 5 27 8 7 0 6'
for n in $string ; do
(( total += n ))
done
echo $total

string=0 4 5 27 8 7 0 6
This attempts to set the variable string to 0, then invoke the command 4 with arguments 5 27 8 7 0 6.
You need to quote the value:
string="0 4 5 27 8 7 0 6"
And you need to remove the quotes when you refer to it; change
for n in "$string"; do
to
for n in $string; do

You should use :
total=$(( total + n ))
no need for the $ before variables inside a $(( )) statement

Related

Problem with if condition on a "random walk" script

I'm trying to make the coordinate "x" randomly move in the interval [-1,1]. However, my code works sometimes, and sometimes it doesn't. I tried ShellCheck but it says "no issues detected!". I'm new to conditionals, am I using them wrong?
I'm running this on the windows subsystem for linux. I'm editing it on nano. Since I have a script that will plot 200 of these "random walks", the code should work consistenly, but I really don't understant why it doesn't.
Here's my code:
x=0
for num in {1..15}
do
r=$RANDOM
if [[ $r -lt 16383 ]]
then
p=1
else
p=-1
fi
if [[ $x -eq $p ]]
then
x=$(echo "$x-$p" | bc )
else
x=$(echo "$x+$p" | bc )
fi
echo "$num $x"
done
I expect something like this:
1 -1
2 0
3 1
4 0
5 1
6 0
7 1
8 0
9 1
10 0
11 -1
12 0
13 1
14 0
15 1
But the usual output is something like this:
1 1
2 0
3 -1
4 0
5 -1
6 0
7 -1
(standard_in) 1: syntax error
8
(standard_in) 1: syntax error
9
(standard_in) 1: syntax error
10
(standard_in) 1: syntax error
11
(standard_in) 1: syntax error
12
(standard_in) 1: syntax error
13
(standard_in) 1: syntax error
14
(standard_in) 1: syntax error
15
Always stopping after a -1.
You can do this with bash:
x=$(( x - p ))
or
(( x -= p ))
and you don't need bc.
Replace x=$(echo "$x-$p" | bc ) with x=$(echo "$x-($p)" | bc ) to avoid echo "-1--1" | bc.
One-liner equivalents to the OP's 18-line random walk script, using bash arithmetic evaluation:
x=0; printf '%-5s\n' {1..15}\ $(( x=(RANDOM%2 ? 1 : -1) * (x==0) ))
x=0; printf '%-5s\n' {1..15}\ $(( x=( x ? 0 : (RANDOM%2 ? 1 : -1) ) ))
Sample output of either, (the 2nd column will vary between runs):
1 -1
2 0
3 -1
4 0
5 1
6 0
7 1
8 0
9 -1
10 0
11 1
12 0
13 -1
14 0
15 -1
How it works:
echo {1..15}\ $(( ...some code... )) prints the numbers 1 to
15 followed by 15 instances of whatever result in the $(( ... )) code returns. One flaw with this approach is that with the resulting 15 pairs of numbers, (e.g. 1 -1, 2 0, etc.), each appears to bash as one string, rather than 30 separate numbers.
(RANDOM%2): the % is a modulo operator and here returns the remainder when divided by 2, which is either 0 or 1.
(x==0): $x can be one of three numbers, but if the previous value of $x was -1 or 1 the only legal random step is 0, so we only need a random number if the previous value of $x was 0.
The if logic is replaced with shortcuts of the form (expr?expr:expr); these use the same logic as the OP script.

Divide an output into multiple variables using shell script

So I have a C program that outputs many numbers. I have to check them all. The problem is, each time I run my program, I need to change seeds. In order to do that, I've been doing it manually and was trying to make a shell script to work around this.
I've tried using sed but couldn't manage to do it.
I'm trying to get the output like this:
a=$(./algorithm < input.txt)
b=$(./algorithm2 < input.txt)
c=$(./algorithm3 < input.txt)
The output of each algorithm program is something like this:
12 13 315
1 2 3 4 5 6 7 8 10 2 8 9 1 0 0 2 3 4 5
So the variable a has all this output, and what I need is
variable a to contain this whole string
and variable a1 to contain only the third number, in this case, 315.
Another example:
2 3 712
1 23 15 12 31 23 3 2 5 6 6 1 2 3 5 51 2 3 21
echo $b should give this output:
2 3 712
1 23 15 12 31 23 3 2 5 6 6 1 2 3 5 51 2 3 21
and echo $b1 should give this output:
712
Thanks!
Not exactly what you are asking, but one way to do this would be to store the results of your algorithm in arrays, and then dereference the item of interest. You'd write something like:
a=( $(./algorithm < input.txt) )
b=( $(./algorithm2 < input.txt) )
c=( $(./algorithm3 < input.txt) )
Notice the extra () that encloses the statements. Now, a, b and c are arrays, and you can access the item of interest like ${a[0]} or $a[1].
For your particular case, since you want the 3rd element, that would have index = 2, hence:
a1=${a[2]}
b1=${b[2]}
c1=${c[2]}
Since you are using the Bash shell (see your tags), you can use Bash arrays to easily access the individual fields in your output strings. For example like so:
#!/bin/bash
# Your lines to gather the output:
# a=$(./algorithm < input.txt)
# b=$(./algorithm2 < input.txt)
# c=$(./algorithm3 < input.txt)
# Just to use your example output strings:
a="$(printf "12 13 315 \n 1 2 3 4 5 6 7 8 10 2 8 9 1 0 0 2 3 4 5")"
b="$(printf "2 3 712 \n 1 23 15 12 31 23 3 2 5 6 6 1 2 3 5 51 2 3 21")"
# Put the output in arrays.
a_array=($a)
b_array=($b)
# You can access the array elements individually.
# The array index starts from 0.
# (The names a1 and b1 for the third elements were your choice.)
a1="${a_array[2]}"
b1="${b_array[2]}"
# Print output strings.
# (The newlines in $a and $b are gobbled by echo, since they are not quoted.)
echo "Output a:" $a
echo "Output b:" $b
# Print third elements.
echo "3rd from a: $a1"
echo "3rd from b: $b1"
This script outputs
Output a: 12 13 315 1 2 3 4 5 6 7 8 10 2 8 9 1 0 0 2 3 4 5
Output b: 2 3 712 1 23 15 12 31 23 3 2 5 6 6 1 2 3 5 51 2 3 21
3rd from a: 315
3rd from b: 712
Explanation:
The trick here is that array constants (literals) in Bash have the form
(<space_separated_list_of_elements>)
for example
(1 2 3 4 a b c nearly_any_string 99)
Any variable that gets such an array assigned, automatically becomes an array variable. In the script above, this is what happens in a_array=($a): Bash expands the $a to the <space_separated_list_of_elements> and reads the whole expression again interpreting it as an array constant.
Individual elements in such arrays can be referenced like variables by using expressions of the form
<array_name>[<idx>]
like a variable name. Therein, <array_name>is the name of the array and <idx> is an integer that references the individual element. For arrays that are represented by array constants, the index counts elements continuously starting from zero. Therefore, in the script, ${a_array[2]} expands to the third element in the array a_array. If the array would have less elements, a_array[2] would be considered unset.
You can output all elements in the array a_array, the corresponding index array, and the number of elements in the array respectively by
echo "${a_array[#]}"
echo "${!a_array[#]}"
echo "${#a_array[#]}"
These commands can be used to track down the fate of the newline: Given the script above, it is still in $a, as can be seen by (watch the quotes)
echo "$a"
which yields
12 13 315
1 2 3 4 5 6 7 8 10 2 8 9 1 0 0 2 3 4 5
But the newline did not make it into the array a_array. This is because Bash considers it as part of the whitespace that separates the third and the fourth element in the array assignment. The same applies if there are no extra spaces around the newline, like here:
12 13 315\n1 2 3 4 5 6 7 8 10 2 8 9 1 0 0 2 3 4 5
I actually assume that the output of your C program comes in this form.
This will store the full string in a[0] and the individual fields in a[1-N]:
$ tmp=$(printf '12 13 315\n1 2 3 4 5 6 7 8 10 2 8 9 1 0 0 2 3 4 5\n')
$ a=( $(printf '_ %s\n' "$tmp") )
$ a[0]="$tmp"
$ echo "${a[0]}"
12 13 315
1 2 3 4 5 6 7 8 10 2 8 9 1 0 0 2 3 4 5
$ echo "${a[3]}"
315
Obviously replace $(printf '12 13 315\n1 2 3 4 5 6 7 8 10 2 8 9 1 0 0 2 3 4 5\n') with $(./algorithm < input.txt) in your real code.

Referencing one number to another number

I have a question which I think is fairly simply but I am new to Bash and can't find much info on this.
5 references 3
10 references 4
20 references 10
30 references 20
inputBeforeLookup = 5 #this the number which needs to look up 3 above^^^^
# 10 would lookup and return 4
#20 returns 10
start = 1
end = $start + $lookupNumberfromFile # 3 in this case, since input was 5
seq $start $end
1
2
3
4
I guess my question here is what is the proper way to create like a configuration file which references numbers to other numbers?
If there is a better way than the snippet of code I posted I am always open to suggestions, like i said I am learning.
I am new to this so I am not sure if the syntax is 100% correct. I am more so looking for a solution on the best way to solve the problem.
Hope this sample helps you regarding variable expansion in bash:
Notice that: the \ prevents the expansion of $$ (current process id). For triple substitution you need double eval and so on....
#!/bin/bash
one=1
two=one
three=two
four=three
five=four
echo $one
eval echo \$$two
eval eval echo \\$\$$three
eval eval eval echo \\\\$\\$\$$four
eval eval eval eval echo \\\\\\\\$\\\\$\\$\$$five
Output:
1
1
1
1
1
Bonus:
In zsh you can use nested substitution much more easily:
#!/bin/zsh
one=1
two=one
three=two
four=three
five=four
echo $one
echo ${(P)two}
echo ${(P)${(P)three}}
...
http://zsh.sourceforge.net/Doc/Release/Expansion.html
Set up an associative array, then test it with numbers 1 to 30. Those numbers that don't reference other numbers are printed as is:
MYMAP=( [5]=3 [10]=4 [20]=10 [30]=20 )
seq 30 | while read x ; do echo ${MYMAP[$x]:-$x} ; done | paste - - - - -
That last | paste - - - - - isn't necessary, but 5 column output is easier to follow given that the input has several multiples of 5. Output:
1 2 3 4 3
6 7 8 9 4
11 12 13 14 15
16 17 18 19 10
21 22 23 24 25
26 27 28 29 20

Why use seq 0 in bash for loop

Why use seq 0 in bash for loop?
for i in `seq 0 $(( ${#ARRAYEX[#]} - 1 ))`
do
echo "ARRAYEX${i}=${ARRAYEX[${i}]}"
done
The seq command generates a sequence of numbers.
For example
seq 0 10
generates a sequence of numbers from 0 up to 10:
0 1 2 3 4 5 6 7 8 9 10
(usually each number is on a new line, but I place them after each other)
In your example a sequence on number starting at 0 up to the size of the array minus 1 is generated.
The seq 0 $(( ${#ARRAYEX[#]} - 1 )) part expands to:
0 1 2 3 4
assuming that the ARRAYEX has a size of 5.
Inside the loop the array is used again, so the loop is iterating over all array element (as the first element of the array starts at 0).
seq 0 $(( ${#ARRAYEX[#]} - 1 )) creates a sequence of all the possible indexes of the array. You can also use
for ((i=0; i<${#ARRAYEX[#]}; ++i )) ; do

Length of a sequence of numbers using seq in shell

I am new to shell scripting and I am trying a simple task of getting the length of a sequence of numbers generated using seq.
With the help of a related post here: How to find the array length in unix shell? I was able to do this -
a=(1 2 3 4 5)
echo ${#a[#]} #length of a
5 #length of a = 5 (This is fine !!)
However when I try to do a similar thing using seq ..
b=$(seq 1 1 10)
echo $b
1 2 3 4 5 6 7 8 9 10
echo ${#b[#]}
1 #the length of b is 1, while I expect it to be 10
Why does this happen ? Are the variable types a and b different? is b not an array ?
I am sure I am missing something very trivial here, help is greatly appreciated.
Thanks
Ashwin
You need to store the output in an array to find the length of the array:
$ b=($(seq 1 1 10))
$ echo ${#b[#]}
10
Saying b=$(seq 1 1 10) doesn't produce an array.
Try
echo ${b[0]}
It will be 1 2 3 4 5 6 7 8 9 10 because all your values are stored in first element of array a as a string.
b=($(seq 1 1 10))
will do what you want.

Resources