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})
Related
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
A standard loop works fine for me
for i in {1..3}
do
echo "$i"
done
Output
1
2
3
But if I replace the constant by a variable (after declaring it, of course) it doesn't.
n=3
for i in {1..$n}
do
echo "$i"
done
Output
{1..3}
Apologies if it's a dumb question. I tried to search on SO and Google but not luck.
Follow up question:
declare -a nOptions={3,4};
for i1 in $(seq 1 ${nOptions[0]});
do
for i2 in $(seq 1 ${nOptions[1]});
do
echo "$i1$i2"
done
done
This gives output
11
21
31
41
and not
11
12
13
14
21
22
23
24
31
32
33
34
as expected.
Curly-brace expansion takes place before parameter expansion (or any other expansion). The literal $variable doesn't look like the other end of a range, so it doesn't trigger the range expansion.
To use a variable count, just use a counting loop:
for (( i=1; i<=$n; ++i )); do
done
On most modern systems (at least Linux and OS X) you can use the seq command, although it's neither part of the POSIX standard nor built-in to Bash itself:
for i in $(seq 1 $n); do
done
If you were for some reason dead-set on using curlies, you could add an eval, but that will make the solution fragile and dangerous.
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
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
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