15

I would like to create a simple for bash loop that iterates over a sequence of numbers with a specific interval and then a different sequence of numbers, e.g.

for i in $(seq 0 5 15)
do
    echo $i
done

But after interating through i=0, 5, 10, 15, I'd like it to iterate through say 30, 35, 40, 45 as well.

Is there a way to do this using seq? Or an alternative?

jub0bs
  • 60,866
  • 25
  • 183
  • 186
Lily
  • 303
  • 1
  • 2
  • 8
  • The actual content of the for loop is more than just echo$i (about 200 lines) I don't want to repeat it and make my script huge! – Lily Sep 09 '15 at 14:36
  • 1
    You can put those 200 lines in a function and call it twice. – anubhava Sep 09 '15 at 14:39
  • Just `seq 0 5 45` and check that it's not 20 or 25 at the top and `continue` if it is? – Kevin Sep 09 '15 at 14:45
  • @Kevin I think your suggestion works for this example, but not in the general case (e.g. if the interval changes). – jub0bs Sep 09 '15 at 14:47
  • 1
    Note that `seq` should rarely, if ever, be used in a shell script. Most modern shells have more efficient alternatives (such as C-style for loops), and a script that requires POSIX compatibility cannot use `seq` because it is not defined by POSIX. (You would use a `while` loop and update the index yourself instead.) – chepner Sep 09 '15 at 15:13

1 Answers1

20

Approach 1

Simply augment the command within $(...) with another call to seq:

for i in $(seq 0 5 15; seq 30 5 45); do
    echo $i
done

and then

$ bash test.sh
0
5
10
15
30
35
40
45

# Approach 2

In your follow-up comment, you write

The actual content of the for loop is more than just echo$i (about 200 lines) I don't want to repeat it and make my script huge!

As an alternative to the approach outlined above, you could define a shell function for those 200 lines and then call the function in a series of for loops:

f() {
   # 200 lines
   echo $i
}

for i in $(seq 0 5 15) do
    f
done

for i in $(seq 30 5 45) do
    f
done

Approach 3 (POSIX-compliant)

For maximum portability across shells, you should make your script POSIX-compliant. In that case, you need have to eschew seq, because, although many distributions provide that utility, it's not defined by POSIX.

Since you can't use seq to generate the sequence of integers to iterate over, and because POSIX doesn't define numeric, C-style for loops, you have to resort to a while loop instead. To avoid duplicating the code related to that loop, you can define another function (called custom_for_loop below):

custom_for_loop () {
  # $1: initial value
  # $2: increment
  # $3: upper bound
  # $4: name of a function that takes one parameter
  local i=$1
  while [ $i -le $3 ]; do
      $4 $i
      i=$((i+$2))
  done
}

f () {
    printf "%s\n" "$1"
}

custom_for_loop 0 5 15 f
custom_for_loop 30 5 45 f
jub0bs
  • 60,866
  • 25
  • 183
  • 186
  • 1
    Also, `for i in $(seq 0 5 15) $(seq 30 5 45)` will work. – chepner Sep 09 '15 at 14:40
  • I wouldn't bother trying to define a new command to wrap the loop, but the loop structure itself looks fine. – chepner Sep 09 '15 at 16:06
  • @chepner Well, I agree that wrapping the loop in a function is overkill, here. However, if there were many similar loops to go through, it would make sense to abstract the loop away. – jub0bs Sep 09 '15 at 16:08