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.
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
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
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})
This is my script which I am running on UNIX(AIX):
$MON date +"%m"
echo 'expr $MON - 2'
Output:
04
expr $MON - 2
I just want to subtract 2 from my current month and display .
I realize that the default shell on AIX is some variant of ksh which doesn't suffer from the same deficiency as bash in the input base, but it is something to bear in mind if you end up encountering this on another platform.
If this is your input:
$MON date +"%m"
echo 'expr $MON - 2'
then you have several issues.
$MON is unset, which causes the invocation of the line date +"%m", which gives the output 04
the echo command does not execute the expr command as you're using the wrong kind of ticks, which causes the output expr $MON - 2
First, to assign the variable you do VARIABLE=value, in your case this should be:
MON=`date +"%m"`
Don't put a space before or after the = sign.
Secondly, to perform the expr, you need to use the backtick(`)
echo `expr $MON - 2`
However, for most shells, you should use the more modern version of i want to get the result of a command, which is the logic $(command). These can be embedded, which makes them a lot easier to understand (backticks require escaping, and the more backticks the more escaping needed which quickly leads to backslash-palooza).
For bash, you need to ensure that the month is interpreted as a base 10 number, as otherwise once you hit August the code will stop working:
To force the number to be interpreted as a base 10 number, precede it with 10#:
MON=10#$(date +"%m")
echo $(($MON-2))
Examples:
bash-3.2$ month=07
bash-3.2$ echo $(($month + 1))
8
bash-3.2$ month=08
bash-3.2$ echo $(($month + 1))
bash: 08: value too great for base (error token is "08")
bash-3.2$ month=10#08
bash-3.2$ echo $(($month + 1))
9
Try this:
MON=$(date +"%m")
echo $(($MON-2))
You don't need to use expr because bash can perform simple arithmetic as well.
If your shell doesn't support arithmetic expressions, use expr:
expr $MON - 2
In both cases, you will get 2 as the output.
Another option: if you want to see 11 or 12 in January or February, let GNU date do the arithmetic:
date -d "-2 months" +%m
Consider the following:
for i in 1 2 3; do echo $(( j += 1 ))& done
According to (my reading of) the sh language spec, section 2.3 paragraph 5, the arithmetic expansion of j += 1 should take place during token recognition, and should thus be processed before the shell ever reads the &. So it seems that executing the above line should increment j by 3 and each invocation of echo should get a different argument. (Which is the behavior if '&' is replaced by ';'). In bash 3.2.25, j is not modified. Is this a bug in bash, or am I misunderstanding something?
That entire token recognition section deals with parsing, not expansion or command evaluation of any kind. Have a look at the "introduction" for context - parsing comes before basically everything, while evaluation of the arithmetic expression is usually one of the very last evaluation steps. It isn't really relevant here.
You're probably actually wondering about whether expansions occur before the subshell forks for asynchronous lists. They don't.
$ ksh93 -c 'typeset -i n; while ((++j%10)); do { n+=1; printf "$n "; } & done; while ((++j%10)); do : $((n++)) ${ printf "$n " >&2;} & done 2>&1; echo'
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
$
$ bash -c 'f() { printf "#%d %s, %s\n" "$#"; }; f 1 $BASHPID $$; f 2 $BASHPID $$ & sleep 1'
#1 12275, 12275
#2 12276, 12275
$
As for the order of expansions, arithmetic expansion is performed at the same time as parameter expansion and command substitution, from left-to-right (also as shown above in the second loop).
I think you're not the only one to misread that section, I've filed a number of bugs related to it which have turned out to be either an error on my part or due to some unfortunate interaction with non-POSIX shell extensions. IMHO that section is too terse.
If you think there's a problem with the spec language or the implementations you'll likely find better answers on one of the mailing lists. ast-users, help-bash, austin group lists