How to pass start variable to for loop, BASH - bash

Good day,
I was wondering how to properly pass a variable to a for loop. Doesn't matter the syntax, I just want to pass the variable and count by two.
The issue:
when I write down:
r=0 ; for i in {"$r"..10..2}; do echo "Welcome $i times" ;done
I get:
Welcome {0..10..2} times
and not:
Welcome 0 times
Welcome 2 times
Welcome 4 times
Welcome 6 times
Welcome 8 times
Welcome 10 times
Thanks in advance for any clue

The general format for a for loop that utilizes variables for loop boundaries is:
#!/bin/bash
a=2
b=10
increment=2
for ((i=$a; i<=$b; i+=$increment)); do
## <something with $i>
echo "i: $i"
done
output:
$ bash forloop.sh
i: 2
i: 4
i: 6
i: 8
i: 10

For the sake of completeness,
In stead of
for i in {"$r"..10..2};
you can try
for i in $(eval echo {$r..10..2});
However, I highly discourage you to use this solution, but go for David's solution.

You can not use variable in {a....b} syntax. But you can use seq.
see this

Related

How to iterate in a range determined by TWO variables?

I have a code:
echo "the range's starting number:"
read -r a #it was 10
echo "the range's ending number:"
read -r b #it was 20
for (( c=$a; c<=$b; c++ ))
do
echo $c
done
Question: what is the working syntax? I found a similar question where c=1; c<=$b; c++ worked. I want to iterate between $a (example $a=10) and $b (example $b=20) and not between 1 and $b=20. Thanks for the help. (the output is blank here, the expected output was:
10
11
12
..
20
I tried my code, also closed terminal and started a new one because of possible caching issues, but there was still a blank output.
Update: in the comments accdias's answer was working. With for ((c=a; c<=b; c++)) i got the expected output. Thanks all for the help and comments!
I took #accdias's comment and figured I would just drop it here as an answer for easy reference:
START=12
END=24
for ((i=START; i<=END; i++)); do
echo $i
done

Read range of numbers into a for loop

So, I am building a bash script which iterates through folders named by numbers from 1 to 9. The script depends on getting the folder names by user input. My intention is to use a for loop using read input to get a folder name or a range of folder names and then do some stuff.
Example:
Let's assume I want to make a backup with rsync -a of a certain range of folders. Usually I would do:
for p in {1..7}; do
rsync -a $p/* backup.$p
done
The above would recursively backup all content in the directories 1 2 3 4 5 6 and 7 and put them into folders named as 'backup.{index-number}'. It wouldn't catch folders/files with a leading . but that is not important right now.
Now I have a similar loop in an interactive bash script. I am using select and case statements for this task. One of the options in case is this loop and it shall somehow get a range of numbers from user input. This now becomes a problem.
Problem:
If I use read to get the range then it fails when using {1..7} as input. The input is taken literally and the output is just:
{1..7}
I really would like to know why this happens. Let me use a more descriptive example with a simple echo command.
var={1..7} # fails and just outputs {1..7}
for p in $var; do echo $p;done
read var # Same result as above. Just outputs {1..7}
for p in $var; do echo $p;done
for p in {1..7}; do echo $p;done # works fine and outputs the numbers 1-7 seperated with a newline.
I've found a workaround by storing the numbers in an array. The user can then input folder names seperated by a space character like this: 1 2 3 4 5 6 7
read -a var # In this case the output is similar to the 3rd loop above
for p in ${var[#]}; do echo $p; done
This could be a way to go but when backing up 40 folders ranging from 1-40 then adding all the numbers one-by-one completely makes my script redundant. One could find a solution to one of the millennium problems in the same time.
Is there any way to read a range of numbers like {1..9} or could there be another way to get input from terminal into the script so I can iterate through the range within a for-loop?
This sounds like a question for google but I am obviously using the wrong patterns to get a useful answer. Most of similar looking issues on SO refer to brace and parameter expansion issues but this is not exactly the problem I have. However, to me it feels like the answer to this problem is going in a similar direction. I fail to understand why when a for-loop for assigning {1..7} to a variable works but doing the same like var={1..7} doesn't. Plz help -.-
EDIT: My bash version:
$ echo $BASH_VERSION
4.2.25(1)-release
EDIT2: The versatility of a brace expansion is very important to me. A possible solution should include the ability to define as many ranges as possible. Like I would like to be able to choose between backing up just 1 folder or a fixed range between f.ex 4-22 and even multiple options like folders 1,2,5,6-7
Brace expansion is not performed on the right-hand side of a variable, or on parameter expansion. Use a C-style for loop, with the user inputing the upper end of the range if necessary.
read upper
for ((i=1; i<=$upper; i++)); do
To input both a lower and upper bound separated by whitespace
read lower upper
for (i=$lower; i <= $upper; i++)); do
For an arbitrary set of values, just push the burden to the user to generate the appropriate list; don't try to implement your own parser to process something like 1,2,20-22:
while read p; do
rsync -a $p/* backup.$p
done
The input is one value per line, such as
1
2
20
21
22
Even if the user is using the shell, they can call your script with something like
printf '%s\n' 1 2 20..22 | backup.sh
It's easier for the user to generate the list than it is for you to safely parse a string describing the list.
The evil eval
$ var={1..7}
$ for i in $(eval echo $var); do echo $i; done
this also works,
$ var="1 2 {5..9}"
$ for i in $(eval echo $var); do echo $i; done
1
2
5
6
7
8
9
evil eval was a joke, that is, as long as you know what you're evaluating.
Or, with awk
$ echo "1 2 5-9 22-25" |
awk -v RS=' ' '/-/{split($0,a,"-"); while(a[1]<=a[2]) print a[1]++; next}1'
1
2
5
6
7
8
9
22
23
24
25

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})

BASH Arithmetic Issues

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

Is it possible to build variable names from other variables in bash? [duplicate]

This question already has answers here:
Dynamic variable names in Bash
(19 answers)
Closed 6 years ago.
I apologise for the pretty terrible title - and the poor quality post - but what I basically want to do is this:
for I in 1 2 3 4
echo $VAR$I # echo the contents of $VAR1, $VAR2, $VAR3, etc.
Obviously the above does not work - it will (I think) try and echo the variable called $VAR$I Is this possible in Bash?
Yes, but don't do that. Use an array instead.
If you still insist on doing it that way...
$ foo1=123
$ bar=foo1
$ echo "${!bar}"
123
for I in 1 2 3 4 5; do
TMP="VAR$I"
echo ${!TMP}
done
I have a general rule that if I need indirect access to variables (ditto arrays), then it is time to convert the script from shell into Perl/Python/etc. Advanced coding in shell though possible quickly becomes a mess.
You should think about using bash arrays for this sort of work:
pax> set arr=(9 8 7 6)
pax> set idx=2
pax> echo ${arr[!idx]}
7
For the case where you don't want to refactor your variables into arrays...
One way...
$ for I in 1 2 3 4; do TEMP=VAR$I ; echo ${!TEMP} ; done
Another way...
$ for I in 1 2 3 4; do eval echo \$$(eval echo VAR$I) ; done
I haven't found a simpler way that works. For example, this does not work...
$ for I in 1 2 3 4; do echo ${!VAR$I} ; done
bash: ${!VAR$I}: bad substitution
for I in {1..5}; do
echo $((VAR$I))
done
Yes. See Advanced Bash-Scripting Guide
This definately looks like an array type of situation. Here's a link that has a very nice discussion (with many examples) of how to use arrays in bash: Arrays

Resources