Evaluate expression inside bash for loop [duplicate] - bash

This question already has answers here:
How do I iterate over a range of numbers defined by variables in Bash?
(20 answers)
Closed 8 years ago.
If I do this, I get the result as expected.
for i in {125..129}; do echo $i; done
125
126
127
128
129
But when I do this? I get something weired.
for i in {$((1+(25-1)*500))..$((25*500))}; do echo $i; done
{12001..12500}
I wish to pass a variable inside the loop variables like $((1+($j-1)*500))

Bash's brace expansion has limitations. What you want is seq:
for i in $( seq $((1+(25-1)*500)) $((25*500)) ); do echo $i; done
The above will loop over all numbers from 12001 to 12500.
Discussion
seq is similar to bash's braces:
$ echo {2..4}
2 3 4
$ echo $(seq 2 4)
2 3 4
The key advantage of seq is that its arguments can include not just arithmetic expressions, as shown above, but also shell variables:
$ x=4; echo $(seq $((x-2)) $x)
2 3 4
By contrast, the brace notation will accept neither.
seq is a GNU utility and is available on all linux systems as well as recent versions of OSX. Older BSD systems can use a similar utility called jot.

Brace expansion is the very first expansion that occurs, before parameter, variable, and arithmetic expansion. Brace expansion with .. only occurs if the values before and after the .. are integers or single characters. Since the arithmetic expansion in your example has not yet occurred, they aren't single characters or integers, so no brace expansion occurs.
You can force reexpansion to occur after arithmetic expansion with eval:
for i in $(eval echo {$((1+(25-1)*500))..$((25*500))}); do echo $i;

You query is very similar to :
shell script "for" loop syntax
brace expansion, {x..y} is performed before other expansions, so you cannot use that for variable length sequences.
Instead try
for i in seq $((1+(25-1)*500)) $((25*500)); do echo $i; done

It is just echoing the text which is exactly as it says:
{12001..12500}
That is "{"+12001+"..."+12500+"}"

Don't do this with a for loop. The {..} notation is not that flexible:
i=$((1+(25-1)*500)); while test $i -le $((25*500)); do echo $((i++)); done

Try this
for (( i= $((1+(25-1)*500)); i<=$((25*500)); i++ )); do echo $i; done
or this
for i in $(seq $(( 1+(25-1)*500 )) $(( 25*500 )) ); do echo $i; done

Related

How can I use a variable within a ".." range, as in {1..${num_lanes}}? [duplicate]

This question already has answers here:
How do I iterate over a range of numbers defined by variables in Bash?
(20 answers)
Variables in bash seq replacement ({1..10}) [duplicate]
(7 answers)
Closed 5 years ago.
#!/bin/sh
for i in {1..5}
do
echo "Welcome"
done
Would work, displays Welcome 5 times.
#!/bin/sh
howmany=`grep -c $1 /root/file`
for i in {1..$howmany}
do
echo "Welcome"
done
Doesn't work! howmany would equal 5 as that is what the output of grep -c would display. $1 is parameter 1 which is specific when running the script.
Any ideas?
Workarounds for not being able to use variables in a sequence brace expression:
If the intent is merely to iterate over numbers in a range - as in the OP's case - the best choice is not to use brace expansion, but instead use bash's C-style loop - see user000001's answer.
If the specific numbers aren't important and you simply need to execute a loop body a specified number of times, Cole Tierney's answer is an option.
If use of brace expansion is desired nonetheless:
If you do NOT need the numbers in the list to have a prefix or postfix, use the seq utility with an unquoted command substitution (small caveat: seq is NOT a POSIX utility, but it is widely available); e.g.
echo $(seq 3) -> 1 2 3; start number 1 implied
echo $(seq -f '%02.f' 3) -> 01 02 03 - zero-padded
echo $(seq 2 4) -> 2 3 4; explicit start and end numbers
echo $(seq 1 2 5) -> 1 3 5; custom increment (the 2 in the middle)
If you DO need the numbers in the list to have a prefix or postfix, you have several choices:
Use the seq utility with its -f option for providing a printf-style format string (as used above for zero-padding), or pure Bash workarounds based on eval (extra care needed!) or building an array in a loop, all of which are detailed in this answer.
You could also consider implementing the functionality generically, such as by writing a custom shell function or a custom script with utilities such as awk or perl.
Example of safe use of eval with variables driving a sequence brace expression:
The variables are validated beforehand, to make sure they contain decimal integers.
from=1 to=3 # sample values
# Ensure that $from and $to are decimal numbers and abort, if they are not.
(( 10#$from + 10#$to || 1 )) 2>/dev/null || { echo "Need decimal integers" >&2; exit 1; }
eval echo "A{$from..$to}" # -> 'A1 A2 A3'
General overview of brace expansion
The main purpose of brace expansion is to expand to a list of tokens with each token having an optional prefix and/or postfix; brace expansions must be unquoted and come in 2 flavors:
a fixed series (list) of comma-separated strings - variables supported
specifies and expands to a fixed number of tokens (2 or more); e.g.:
echo A{b,c,d} -> Ab Ac Ad, i.e., 3 tokens, as implied by the number of args.
echo {/,$HOME/}Library e.g., -> /Library /User/jdoe/Library
Variable references - and even globs - are supported, but note that they get expanded after brace expansion, in its result, in the course of normal evaluation.
a sequence expression (range) with .., typically numerical - variables NOT supported
expands to a variable number of tokens, driven by literal start and end points (for historical reasons, use of variables is NOT supported - see the comments on user000001's answer):
[rare] strings: only single English letters allowed; e.g. {a..c}
numbers: decimal integers only; e.g., {1..10}, {10..1}, {-1..2}
example with prefix and postfix: A{1..3}# -> A1# A2# A3#
broken example with variables: {$from..$to} # !! FAILS - $from and $to are interpreted as literals and therefore not recognized as either a single letter or a decimal integer - no brace expansion is performed (see below).
by contrast, using variables does work in zsh and ksh.
bash 4+ adds two features:
optional increment step value:
echo A{1..5..2} -> A1 A3 A5 - numbers incremented by 2
ability to zero-pad:
echo A{001..003} -> A001 A002 A003
An invalid brace expression is not expanded (treated like a regular unquoted string, with { and } treated as literals):
echo {} -> '{}' - invalid as a brace expr.: at least 2 ,-separated tokens needed
This allows the use of unquoted {} with find, for instance.
echo {1..$to} -> '{1..<value-of-$to>}' - invalid as a brace expr. in bash: variables not supported; however, valid in ksh and zsh.
(fish, by contrast, expands any {...} sequence; similarly, zsh has option BRACE_CCL (OFF by default) for expanding individual characters inside {..}, which effectively causes expansion of any nonempty {...} sequence.)
The brace expansion is evaluated before the variables are expanded. You need a c-style for loop instead:
for ((i=1;i<=howmany;i++))
do
echo "Welcome"
done
create a sequence to control your loop
for i in $(seq 1 $howmany); do
echo "Welcome";
done
The problem is that the "brace expansion" is performed before the "variable expansion"
for i in $(seq 1 $howmany)
works as #damienfrancois said, or, if you would like:
for i in $(eval echo "{$start..10}")
probably does, but don't use it for everyone's sanity.
You could also use a while loop:
while ((howmany--)); do
echo "Welcome"
done
We could also use eval in this case:
howmany=`grep -c $1 /root/file`
for i in $(eval echo {1..$howmany}); do
echo "Welcome"
done

Bash - how to make arithmetic expansion in range braces?

I want to do this:
for i in {1.."$((2**3))"}; do echo "$i"; done
But that would output {1..8}, which I want to execute, not output.
How to?
You could to use seq instead of range braces:
for i in $(seq 1 $((2**3))); do echo "$i"; done
You can't do in like that in bash, brace expansion happens before variable does.
A c-style for loop can be an alternative.
for ((i = 1; i <= 2**3; i++)); do printf '%d ' "$i"; done
... Or if you really want to do the brace expansion use eval which is not advised to use but it is the only way...
eval echo {1..$((2**3))}
See the local bash manual for the order of expansion PAGER='less +/^EXPANSION' man bash and the online manual (thanks to #Freddy) https://www.gnu.org/software/bash/manual/html_node/Shell-Expansions.html
See eval in the local bash manual PAGER='less +/^[[:blank:]]*eval\ ' man bash

Bash for loop fails when sequence end is a variable [duplicate]

This question already has answers here:
How do I iterate over a range of numbers defined by variables in Bash?
(20 answers)
Variables in bash seq replacement ({1..10}) [duplicate]
(7 answers)
Closed 5 years ago.
#!/bin/sh
for i in {1..5}
do
echo "Welcome"
done
Would work, displays Welcome 5 times.
#!/bin/sh
howmany=`grep -c $1 /root/file`
for i in {1..$howmany}
do
echo "Welcome"
done
Doesn't work! howmany would equal 5 as that is what the output of grep -c would display. $1 is parameter 1 which is specific when running the script.
Any ideas?
Workarounds for not being able to use variables in a sequence brace expression:
If the intent is merely to iterate over numbers in a range - as in the OP's case - the best choice is not to use brace expansion, but instead use bash's C-style loop - see user000001's answer.
If the specific numbers aren't important and you simply need to execute a loop body a specified number of times, Cole Tierney's answer is an option.
If use of brace expansion is desired nonetheless:
If you do NOT need the numbers in the list to have a prefix or postfix, use the seq utility with an unquoted command substitution (small caveat: seq is NOT a POSIX utility, but it is widely available); e.g.
echo $(seq 3) -> 1 2 3; start number 1 implied
echo $(seq -f '%02.f' 3) -> 01 02 03 - zero-padded
echo $(seq 2 4) -> 2 3 4; explicit start and end numbers
echo $(seq 1 2 5) -> 1 3 5; custom increment (the 2 in the middle)
If you DO need the numbers in the list to have a prefix or postfix, you have several choices:
Use the seq utility with its -f option for providing a printf-style format string (as used above for zero-padding), or pure Bash workarounds based on eval (extra care needed!) or building an array in a loop, all of which are detailed in this answer.
You could also consider implementing the functionality generically, such as by writing a custom shell function or a custom script with utilities such as awk or perl.
Example of safe use of eval with variables driving a sequence brace expression:
The variables are validated beforehand, to make sure they contain decimal integers.
from=1 to=3 # sample values
# Ensure that $from and $to are decimal numbers and abort, if they are not.
(( 10#$from + 10#$to || 1 )) 2>/dev/null || { echo "Need decimal integers" >&2; exit 1; }
eval echo "A{$from..$to}" # -> 'A1 A2 A3'
General overview of brace expansion
The main purpose of brace expansion is to expand to a list of tokens with each token having an optional prefix and/or postfix; brace expansions must be unquoted and come in 2 flavors:
a fixed series (list) of comma-separated strings - variables supported
specifies and expands to a fixed number of tokens (2 or more); e.g.:
echo A{b,c,d} -> Ab Ac Ad, i.e., 3 tokens, as implied by the number of args.
echo {/,$HOME/}Library e.g., -> /Library /User/jdoe/Library
Variable references - and even globs - are supported, but note that they get expanded after brace expansion, in its result, in the course of normal evaluation.
a sequence expression (range) with .., typically numerical - variables NOT supported
expands to a variable number of tokens, driven by literal start and end points (for historical reasons, use of variables is NOT supported - see the comments on user000001's answer):
[rare] strings: only single English letters allowed; e.g. {a..c}
numbers: decimal integers only; e.g., {1..10}, {10..1}, {-1..2}
example with prefix and postfix: A{1..3}# -> A1# A2# A3#
broken example with variables: {$from..$to} # !! FAILS - $from and $to are interpreted as literals and therefore not recognized as either a single letter or a decimal integer - no brace expansion is performed (see below).
by contrast, using variables does work in zsh and ksh.
bash 4+ adds two features:
optional increment step value:
echo A{1..5..2} -> A1 A3 A5 - numbers incremented by 2
ability to zero-pad:
echo A{001..003} -> A001 A002 A003
An invalid brace expression is not expanded (treated like a regular unquoted string, with { and } treated as literals):
echo {} -> '{}' - invalid as a brace expr.: at least 2 ,-separated tokens needed
This allows the use of unquoted {} with find, for instance.
echo {1..$to} -> '{1..<value-of-$to>}' - invalid as a brace expr. in bash: variables not supported; however, valid in ksh and zsh.
(fish, by contrast, expands any {...} sequence; similarly, zsh has option BRACE_CCL (OFF by default) for expanding individual characters inside {..}, which effectively causes expansion of any nonempty {...} sequence.)
The brace expansion is evaluated before the variables are expanded. You need a c-style for loop instead:
for ((i=1;i<=howmany;i++))
do
echo "Welcome"
done
create a sequence to control your loop
for i in $(seq 1 $howmany); do
echo "Welcome";
done
The problem is that the "brace expansion" is performed before the "variable expansion"
for i in $(seq 1 $howmany)
works as #damienfrancois said, or, if you would like:
for i in $(eval echo "{$start..10}")
probably does, but don't use it for everyone's sanity.
You could also use a while loop:
while ((howmany--)); do
echo "Welcome"
done
We could also use eval in this case:
howmany=`grep -c $1 /root/file`
for i in $(eval echo {1..$howmany}); do
echo "Welcome"
done

Expr and count in shell

I learn a shell code from a video,and there is a code:
#!/bin/sh
i=1
while true
do
echo $i
i='expr $i + 1'
done
The code above gives me output is:
1
expr $i + 1
expr $i + 1
expr $i + 1
......
But in the video the output are consistent numbers,why the same code shows different results?
As aready mentioned in a comment, you have the wrong quotes; the video will have had backticks (`, ASCII 96), not single quotes (', ASCII 39).
Additionally, anything which uses this obsolescent syntax for command substitution or expr is at least 20 years behind, and should be avoided in favor of modern POSIX constructs.
#!/bin/sh
i=1
while true
do
echo "$i" # Note proper quoting
i=$((i+1))
done
In Bash, you could use brace expansion or a C-style for loop, but these are not portable to modern sh.
If you need your code to be portable to pre-POSIX systems, my recommendation would still be to learn the modern constructs first, especially for simple code like this (where in a production system you would probably want to use Awk or Perl for a loop like this anyway).

Assigning variables in for loop in bash

The following bash script works fine to print numbers from 1 to 10:
for i in {1..10}
do
echo "$i"
done
But if I want to make the upper limit a variable, then this script does not works.
i=10
for j in {1..$i}
do
echo "$j"
done
Can anyone suggest please how to make the second script work?
Brace expansion happens before any other expansion.
You can say:
for j in $(seq 1 $i); do echo "$j"; done
Quoting from the link above:
Brace expansion is performed before any other expansions, and any
characters special to other expansions are preserved in the result. It
is strictly textual. Bash does not apply any syntactic interpretation
to the context of the expansion or the text between the braces. To
avoid conflicts with parameter expansion, the string ‘${’ is not
considered eligible for brace expansion.
You cannot use variables in {..} directive of Bash. Use BASH arithmetic operator ((...)) like this:
i=10
for ((j=1; j<=i; j++)); do
echo "$j"
done

Resources