BASH Arithmetic Issues - bash

I'm working in BASH and I'm having an idiot moment right now. I've got a project I'm working on that I'm going to need to use some very basic arithmetic expressions and I just realized that a lot of my problems with it are because my variables are not updating. So I threw together a basic algorithm that increments a variable by another variable with a while loop until a certain number is reached.
counter=1
counter2=0
while [[ counter2 < 10 ]]; do
counter2=$(($counter2+$counter))
echo $counter
echo $counter2
done
I run the script. Does nothing. I set the < to > just for kicks and an infinite loop occurs with a repeated output of:
1
0
1
0
Forever and ever until I stop it. So it's obvious the variables are not changing. Why? I feel like such an idiot because it must be something stupid I'm overlooking. And why, when I have <, it also isn't an infinite loop? Why doesn't it print anything at all for that matter? If counter2 is always less than 10, why doesn't it just keep going on forever?
Thanks folks in advance.
EDIT: Well, I realize why it wasn't outputting anything for when the check is <... I should have been using $counter2 instead of just counter2 to get the actual value of counter2. But now it just outputs:
1
2
And that's it... I feel like such a derp.

If this is all bash (100% sure) then you could use declare -i in order to explicitly set type of your variables and then your code will be as simple as :
declare -i counter=1
declare -i counter2=0
while [[ $counter2 -lt 10 ]]; do
counter2=$counter2+$counter
echo $counter
echo $counter2
done
EDIT:
In bash, you can do arithmatic comparison with double paranethesis. So, your while can be written as:
while (($counter2 < 10)) ; do

Inside the $((...)), don't use the sigil ($).
counter2=$((counter2+counter))

In bash, you can use c-like for loops:
for (( counter2=0; counter2<10; counter2+=counter ))
do
echo $counter": "$counter2
done
Often you will find this construct more appealing to use:
for counter2 in {0..9}
do
echo $counter": "$counter2
done

Related

Infinite 'for' loop with Bash

I have a script like
#!/bin/bash
for i in {1..xx};do break="$i"
If....; then Some command
else break;fi
done
I need something which can repeat this script n times with incrementing $i.
I tried this:
For (( ; ; )); do i=1 && echo $i && ((i++));done
But this always shows 1, not an incrementing number. I also tried $((i+=1)).
Where xx is must be endless number.
Where break="$i" gives me how many times repeated script.
Using for to create an endless loop is unidiomatic, but not hard. Just make the ending condition never true; or, trivially, omit it.
for((i=0; ;++i)); do
echo "$i"
done
The above is Bash only. The usual solution, which works in POSIX sh too, is to use while true (but then that doesn't come with an incrementing index, if that's really what you need).

For loop with an argument based range

I want to run certain actions on a group of lexicographically named files (01-09 before 10). I have to use a rather old version of FreeBSD (7.3), so I can't use yummies like echo {01..30} or seq -w 1 30.
The only working solution I found is printf "%02d " {1..30}. However, I can't figure out why can't I use $1 and $2 instead of 1 and 30. When I run my script (bash ~/myscript.sh 1 30) printf says {1..30}: invalid number
AFAIK, variables in bash are typeless, so how can't printf accept an integer argument as an integer?
Bash supports C-style for loops:
s=1
e=30
for i in ((i=s; i<e; i++)); do printf "%02d " "$i"; done
The syntax you attempted doesn't work because brace expansion happens before parameter expansion, so when the shell tries to expand {$1..$2}, it's still literally {$1..$2}, not {1..30}.
The answer given by #Kent works because eval goes back to the beginning of the parsing process. I tend to suggest avoiding making habitual use of it, as eval can introduce hard-to-recognize bugs -- if your command were whitelisted to be run by sudo and $1 were, say, '$(rm -rf /; echo 1)', the C-style-for-loop example would safely fail, and the eval example... not so much.
Granted, 95% of the scripts you write may not be accessible to folks executing privilege escalation attacks, but the remaining 5% can really ruin one's day; following good practices 100% of the time avoids being in sloppy habits.
Thus, if one really wants to pass a range of numbers to a single command, the safe thing is to collect them in an array:
a=( )
for i in ((i=s; i<e; i++)); do a+=( "$i" ); done
printf "%02d " "${a[#]}"
I guess you are looking for this trick:
#!/bin/bash
s=1
e=30
printf "%02d " $(eval echo {$s..$e})
Ok, I finally got it!
#!/bin/bash
#BSD-only iteration method
#for day in `jot $1 $2`
for ((day=$1; day<$2; day++))
do
echo $(printf %02d $day)
done
I initially wanted to use the cycle iterator as a "day" in file names, but now I see that in my exact case it's easier to iterate through normal numbers (1,2,3 etc.) and process them into lexicographical ones inside the loop. While using jot, remember that $1 is the numbers amount, and the $2 is the starting point.

binary execution in a while loop.for loop through shell script

Hello i am new to shell script. want to execute a binary through loop in a shell script.
wrote a pgm which looked like:
i="1"
while [ $i -lt 100 ]
do
/home/rajni/BUFFER_SEND_STUB/build/buffer_send.exe
i=`expr $i +1`
done
doubt it is not working fine. Can anyone suggest????
Thanks.
expr won't like the fact you've used +1 rather than the space-separated + 1.
I also tend to use [[ and ]] rather than the single ones since they're definitely bash-internal and more powerful than the external [/test.
In any case, there's a more efficient way if you're using a relatively recent bash:
for i in {1..100} ; do
echo $i
done
which will do something with each value 1 through 100 inclusive (your current loop does 1 through 99 so you may have to adjust for that).
Changing that 100 to a 5 shows how it works, generating:
1
2
3
4
5
you can use the for loop
for i in {1..100}
do
/home/rajni/BUFFER_SEND_STUB/build/buffer_send.exe
done

for loops with variables in range won't work

So I was writing a for loop and getting some errors, to get an understanding of the errors I wrote this
#! /bin/bash
b=${1:- 10}
echo $b
for i in {0..$b}
do
echo "$i"
done
so if I run ./forloop.sh 10
I get
10
{0..10}
why doesn't the range work when I have a variable as the second argument?
Bash doesn't expand the range. Use this instead.
for (( i=0; i<=$b; i++))
The part of bash that expands things like {1..10} into 1 2 3 4 5 6 7 8 9 10 runs before any parameters like $b are replaced by their values. Since {1..$b} doesn't look like a numeric range, it doesn't get expanded. By the time parameter expansion turns it into {1..10}, it's too late; nothing is going to come along and evaluate the curly-brace expression.
Change the script to use the following (http://ideone.com/MwAi16).
b=10
for i in $(eval echo {0..$b})

Shell loops using non-integers?

I wrote a .sh file to compile and run a few programs for a homework assignment. I have a "for" loop in the script, but it won't work unless I use only integers:
#!/bin/bash
for (( i=10; i<=100000; i+=100))
do
./hw3_2_2 $i
done
The variable $i is an input for the program hw3_2_2, and I have non-integer values I'd like to use. How could I loop through running the code with a list of decimal numbers?
I find it surprising that in five years no one ever mentioned the utility created just for generating ranges, but, then again, it comes from BSD around 2005, and perhaps it wasn't even generally available on Linux at the time the question was made.
But here it is:
for i in $(seq 0 0.1 1)
Or, to print all numbers with the same width (by prepending or appending zeroes), use -w. That helps prevent numbers being sent as "integers", if that would cause issues.
The syntax is seq [first [incr]] last, with first defaulting to 1, and incr defaulting to either 1 or -1, depending on whether last is greater than or less than first. For other parameters, see seq(1).
you can use awk to generate your decimals eg steps of0.1
num=$(awk 'BEGIN{for(i=1;i<=10;i+=0.1)print i}')
for n in $num
do
./hw3_2_2 $n
done
or you can do it entirely in awk
awk 'BEGIN{cmd="hw3_2_2";for(i=1;i<=10;i+=0.1){c=cmd" "i;system(cmd) } }'
The easiest way is to just list them:
for a in 1.2 3.4 3.11 402.12 4.2 2342.40
do
./hw3_2_2 $a
done
If the list is huge, so you can't have it as a literal list, consider dumping it in a file and then using something like
for a in $(< my-numbers.txt)
do
./hw3_2_2 $a
done
The $(< my-numbers.txt) part is an efficient way (in Bash) to substitute the contents of the names file in that location of the script. Thanks to Dennis Williamson for pointing out that there is no need to use the external cat command for this.
Here's another way. You can use a here doc to include your data in the script:
read -r -d '' data <<EOF
1.1
2.12
3.14159
4
5.05
EOF
for i in "$data"
do
./hw3_2_2 "$i"
done
Similarly:
array=(
1.1
2.12
3.14159
4
5.05
)
for i in "${array[#]}"
do
./hw3_2_2 "$i"
done
I usually also use "seq" as per the second answer, but just to give an answer in terms of a precision-robust integer loop and then bc conversion to a float:
#!/bin/bash
for i in {2..10..2} ; do
x=`echo "scale=2 ; ${i}/10" | bc`
echo $x
done
gives:
.2
.4
.6
.8
1.0
bash doesn't do decimal numbers. Either use something like bc that can, or move to a more complete programming language. Beware of accuracy problems though.

Resources