LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (https://www.linuxquestions.org/questions/programming-9/)
-   -   Using a variable to control a BASH for loop (https://www.linuxquestions.org/questions/programming-9/using-a-variable-to-control-a-bash-for-loop-737458/)

JohnE1 07-03-2009 07:31 AM

Using a variable to control a BASH for loop
 
I know of 4 different ways to use a for loop:

1. for I in {1..10}; do echo $I; done|

2. for I in 1 2 3 4 5 6 7 8 9 10; do echo $I; done|

3. for I in $(seq 1 10); do echo $I; done|

4. for ((I=1; I <= 10 ; I++)); do echo $I; done

I have a script which uses the 1st form of for loop. I'm trying to modify it to use a variable instead of a static hard-coded value in the section that controls the looping.of the for loop.

I've tried all different ways of quoting and escaping the variable, and the problem is that the quoting chars and escape char are being translated and passed into the loop along with the value stored in the variable.

For example, to change the start value of 1 to whatever value I want passed in through a variable:

Change: for i in {1..100}; do <something>; done

to: for i in {$a..10}; do <something>; done

I have tried: {{$a}..10} and {`$a`..10}, to have the variable evaluated first.
I have tried using the eval() function.
I have tried single and double quotes and the backslash escape character.

Nothing I've tried works. It's probably a syntax error, but as of now, I'm baffled.

Can anyone shed some light on the problem and solution, please? Thanks!

konsolebox 07-03-2009 07:58 AM

i don't really know what you mean but perhaps is it something like this?:

Code:

j=$(( RANDOM % 10 ))
for (( i = j; i <= 10; i++ )); do
    : do something
done


pixellany 07-03-2009 08:00 AM

Interesting---it seems that the stuff in braces gets expanded before the variable is evaluated. I can't find a way around it.

Have you looked in ABS? (Advanced Bash Scripting Guide---on tldp.org)

colucix 07-03-2009 08:06 AM

The problem is that the "brace expansion" is performed before the "variable expansion". See here for details about shell expansion, where they are listed in the order that they are expanded. Hence you have to use eval to let the bash do the variable substitution and then expand the resulting "extended brace" {1..10}. This should work
Code:

#!/bin/bash
start=1
for i in $(eval echo {$start..10})
do
  echo $i
done

otherwise use the forms 3 or 4:
Code:

#!/bin/bash
start=1
for i in $(seq $start 10)
do
  echo $i
done
for (( i = $start; i <= 10 ; i++ ))
do
  echo $i
done


pixellany 07-03-2009 08:20 AM

Yep!!

Here's the quote from ABS..
Quote:

# Unfortunately, brace expansion does not lend itself to parameterization.
var1=1
var2=5
echo {$var1..$var2}

konsolebox 07-03-2009 08:34 AM

following colucix's code, you can also save it in an array if you don't want to summon a subshell like:
Code:

eval "array=({${start}..10})"
eval "set -- {${start}..10}"

and do
Code:

for a in "${array[@]}"; do ...
for a in "$@"; do ...

Unless brace expansion does not work within array assignments, the code above that assigns varlues for an array should work. Sorry i don't have access to a terminal now :).

colucix 07-03-2009 08:52 AM

Quote:

Originally Posted by konsolebox (Post 3595527)
Unless brace expansion does not work within array assignments, the code above that assigns values for an array should work. Sorry i don't have access to a terminal now :).

Yes, it works. Good tip! :)

H_TeXMeX_H 07-03-2009 10:59 AM

But option # 3 above works, why do you have to use the expansion method ? Just to make it harder ?

pixellany 07-03-2009 11:41 AM

If I understand this correctly, the issue with the expansion is that the expansion happens before the parameter substitution. Thus
{$start...7}
does not get expanded correctly.

Why then does the array method work?

konsolebox 07-03-2009 11:55 AM

Quote:

Originally Posted by pixellany (Post 3595715)
Why then does the array method work?

simply put it that brace expansion does not parse variables and only raw data therefore we have to make the line *reappear* so that the variable will turn into a raw data:

if this works:
Code:

a=({1..10})
then evaluating the variable first then reparsing the line should work:
if a = 1:
Code:

eval "array=({$a..10})"
is just the same as
Code:

array=({1..10})
by the way
Quote:

{$start...7}
shouldn't that have 2 dots only?

pixellany 07-03-2009 12:49 PM

Got it!!!---I had not appreciated exactly what "eval" does.

2 dots--of course. I was typing in a dark room.

JohnE1 07-03-2009 02:20 PM

Quote:

Originally Posted by H_TeXMeX_H (Post 3595671)
But option # 3 above works, why do you have to use the expansion method ? Just to make it harder ?

Good question and good point; so, let me give a good reply.

I already have working scripts using form #1 'for' loops. That was the main reason to try and stick with it.

Other related reasons are:

1. A minor edit would be simple to do globally using Vim,

2. I want to understand the inner workings of BASh;

I've never been one to program 'by example' alone.

Easy maintenance and gaining additional knowledge were good enough reasons for me, but NOT to make it harder.

I never said I wasn't willing, or prepared, to switch to one of the other forms of the 'for' loop.

Hope that clears it up.

JohnE1 07-03-2009 02:23 PM

Quote:

Originally Posted by colucix (Post 3595493)
The problem is that the "brace expansion" is performed before the "variable expansion". See here for details about shell expansion, where they are listed in the order that they are expanded. Hence you have to use eval to let the bash do the variable substitution and then expand the resulting "extended brace" {1..10}. This should work
Code:

#!/bin/bash
start=1
for i in $(eval echo {$start..10})
do
  echo $i
done

otherwise use the forms 3 or 4:
Code:

#!/bin/bash
start=1
for i in $(seq $start 10)
do
  echo $i
done
for (( i = $start; i <= 10 ; i++ ))
do
  echo $i
done


Thanks for all that great info, colucix!

That really helps alot.

JohnE1 07-03-2009 02:34 PM

Quote:

Originally Posted by pixellany (Post 3595509)
Yep!!

Here's the quote from ABS..

Thanks for looking that up and sharing it, pixellany (moderator).

H_TeXMeX_H 07-03-2009 02:50 PM

Well, technically I tend to use # 3 because it's more versatile, and you won't have problems like this one. If I were writing scripts that I would have to update later ... that would be the one I'd pick. And, of course, I don't disagree finding out why # 1 causes problems, that helps to understand the inner workings, but in the end I wouldn't use that option.


All times are GMT -5. The time now is 04:19 PM.