Assigning variables in for loop in bash - 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

Related

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

Evaluate expression inside bash for loop [duplicate]

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

Joining files with cat

i'm doing a script to download one file in multiple parts, i'm in the part of joining that files. I can join the parts outside of the script with
cat name.part{0..4} > name.ext
But, if i use this in the script
cat $filename.part{0..$N} > $filename
i get:
cat: Bitcoin-960x623.jpg.part{0..5}: No such file or
{0..4} is a brace expansion; $N is a variable expansion. Your expression doesn't work as you expect because brace expansion happens before variable expansion:
The order of expansions is: brace expansion, tilde expansion, parameter, variable, and arithmetic expansion and command substitution (done in a left-to-right fashion), word splitting, and filename expansion.
http://www.gnu.org/software/bash/manual/bashref.html#Shell-Expansions
You can use the seq command instead of a brace expansion to generate the required filenames up to arbitrary N:
$ filename=myfile
$ N=4
$ cat $(seq -f "$filename.part%g" 0 $N) > name.ext
$
In the unlikely event seq -f is unavailable, you can use a for loop one-liner instead:
$ cat $(for ((i=0; i<N; i++)); do echo "$filename.part$i"; done) > name.ext
$
I solved with this.
for (( k=0 ; k<=$N ; k++ ))
do
cat $filename.part$k >> $filename
done
You cannot use variables inside curly braces in shell. eval can be used here:
eval "$filename.part{0..$N} > $filename"
Assertion: I'm going to argue that this answer is better. (Something I don't do often or lightly.) My rationale is that all of the other answers here at the time of this edit just expand to a range of integers, whether the files matching those integers exist or not. By using glob expansions you guarantee you will not encounter No such file or directory from cat. In all other cases with brace expansion, eval, seq and a for loop you'd have to explicitly check.
If there are fewer than 10 total filenames:
cat "$filename".part[0-9]
will expand as a glob to however many names there are. This is a fast and portable solution, but obviously limited. If there are more than 10 then that pattern will only match the ones with one digit, so you have to get a bit wilier and use extglob:
shopt -s extglob
cat "$filename".part+([0-9])
should do it, but this will only work for the Bourne-Again SHell. (That said, several other modern shells have expansion capabilities similar to extglob.)

Bash: Reading files with defined file extensions in a loop

While this code works
#!/bin/bash
d="test_files/*"
for f in $d.{mp3,txt} ;do
do something
done
putting the {mp3,txt} in to a variable does not, see code below.
#!/bin/bash
a={mp3,txt}
d="test_files/*"
for f in $d."$a" ;do
do smoething
done
the output here is /*.{mp3,txt}
Putting {mp3,txt} in to an array
a=({mp3,txt})
outputs only files with the *.mp3 extension.
It doesn't work because brace expansion happens before all other expansions.
From man bash:
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 can use eval to do brace expansion stored in variables, but it is not recommended. For example:
eval echo "$d.$a"

bash's brace-expansion didn't work

i have a simple problem with bash's brace expansion:
#!/bin/bash
PICS="{x1,x2,x3}.jpg {y1,y2}.png"
for i in $PICS
do
echo $i
done
but the result is:
{x1,x2,x3}.jpg
{y1,y2}.png
But i want the result is: x1.jpg x2.jpg x3.jpg y1.png y2.png
what should i do ?
The straightforward way is
#!/bin/bash
for i in {x1,x2,x3}.jpg {y1,y2}.png; do
echo $i
done
Brace expansion is performed while parsing the line, and will not happen inside quotes.
Brace and wildcard expansion is performed for arguments when a command is evaluated. Change the first line to:
PICS=$(echo {x1,x2,x3}.jpg {y1,y2}.png)
These are files which already exist? If yes, you probably want a (ext)glob. E.g.
printf '%s\n' [xy]+([[:digit:]]).#(jp|pn)g
Brace expansion in Bash is the first expansion step. It occurs mostly in unquoted contexts, though the exact rules are complex. You cannot store one in a string unless you eval the result later.
printf '%s\n' {x{1..3}.jp,y{1,2}.pn}g
These can be defined however you feel. See other answers for less obfuscated options.
You also need to quote your expansions.

Resources