3

How can I build an inner loop such that the loop condition depends on the outer loop run?

My very situation does probably not matter for the code I am looking for, but here it is: I have a crawler that I want to run on changing URLs, where the URL depends on two parameters. The first one is the year and the second one are pages, while the range of pages vary from year to year.

Here is what I tried so far

#!/bin/bash

numbers2004={625..721}
numbers2005={723..823}

for year in 2004 2005
do
  for number in numbers$year
  do
    echo "$year $number"
  done
done

It should give me

2004 625
2004 626
...
2004 720
2004 721
2005 723
2005 724
...
2005 822
2005 823
2

With current bash version:

#!/bin/bash

declare -A numbers    # declare associative array
printf -v numbers[2004] "%s " {625..721}
printf -v numbers[2005] "%s " {723..823}

for year in 2004 2005
do
  for number in ${numbers[$year]}
  do
    echo "$year $number"
  done
done
  • People must not execute the script with sh (at least not on Ubuntu), as I did in the beginning. The reason is explained in askubuntu.com/a/28325/334172. – MERose Dec 17 '14 at 15:39
1

Here's a variant on Cyrus's answer that uses parameter indirection. However, as the link says, array-based approaches are to be preferred over the use of indirection, as such indirection is a close cousin of eval, which should be avoided whenever possible.

(I've reduced the ranges of the numbers from those given in the OP just to make the output a bit smaller).

#!/usr/bin/env bash

printf -v numbers2004 "%d " {625..635}
printf -v numbers2005 "%d " {723..733}

for year in 2004 2005
do
    numbers="numbers$year"
    for number in ${!numbers}
    do
        printf "%s %s\n" "$year" "$number"
    done
done

output

2004 625
2004 626
2004 627
2004 628
2004 629
2004 630
2004 631
2004 632
2004 633
2004 634
2004 635
2005 723
2005 724
2005 725
2005 726
2005 727
2005 728
2005 729
2005 730
2005 731
2005 732
2005 733
  • eval shouldn't be avoided if the alternative is a degradation of portability and/or performance. Moreover, learning to use eval - and the order in which portions of an eval'd statement applied and why they apply that way - is a sure-fire path toward better understanding of the shell itself. – mikeserv Dec 17 '14 at 7:07
  • Sure. And the use of eval in your & Joseph R.'s answers is perfectly safe, since there's no danger of malicious data getting evaled. Still, eval is a tool that needs to be used with caution, and even people who ought to know better have written scripts that eval unsafe data. So I prefer to encourage people to look for solutions that don't require eval, just to be on the safe side. – PM 2Ring Dec 17 '14 at 7:21
  • I don't like the use of eval in my answer - it was used mainly to show how silly relying on brace expansions as iterables can be - it only really comes clear when you consider that the shell has to generate every iterable then iterate over each (same goes for printf -v {expand}). And so eval is not helpful from either a performance standpoint, or - because POSIX requires a certain subset of C mathematics operators and function for shell arithmetic - a portability standpoint. In any case, eval is dangerous when unknown values are eval'd, not otherwise. – mikeserv Dec 17 '14 at 8:22
0

You can also achieve this with variable indirection:

#!/bin/bash

numbers2004="$(printf "%s " {625..721})"
numbers2005="$(printf "%s " {723..823})"

for year in 2004 2005
do
  for number in $(eval echo \$numbers$year)
  do
    echo "$year $number"
  done
done
0

You need eval to do it the way you're trying to do...

numbers2004={625..721}
numbers2005={723..823}

for year in 2004 2005
do
  eval 'eval "for number in '"\$numbers$year"'
  do
    echo \"\$year \$number\"
  done"'
done

...which prints...

2004 625
...
2004 721
2005 723
...
2005 823

But that is kind of a horrible way to do it - and not only because the quoting is a nightmare - but probably most of all because your shell does all of the work twice. It must first generate all of the {brace..expanded} iterables before then iterating over each.

Instead maybe:

y=4 n=623 c=721
while  [ "$((y+=$c<(c+=102*(c<(n+=1<<(c==n))))))" -lt 6  ] 
do    echo "200$y $n"
done

...which prints the same in recent versions of busybox ash, ksh93, dash, yash, bash, zsh, mksh, and posh.

The arithmetic expression can be made slightly more performant for most of those by evaluating parts of it only when necessary like...

y=4 n=623 c=721
while [ "$((c<(n+=1)?(y+=(n+=1)<(c+=102)):y))" -lt 6 ] 
do echo "200$y $n"
done

...which works identically in all of the aforementioned shells excepting busybox. It seems busybox always evaluates all sides of the if expr ? true : false ternary statement and so fails to iterate as expected.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.