8

I'm trying to run through two sequences in the same loop in my shell like below:

#!/bin/bash
for i in (1..15) and (20..25) ;
do
     echo $i
     ......
     .....other process
done

any idea how I can achieve this?

  • @zanna -- my first thought is that the boolean "and" is exclusive, meaning that the result are the numbers that exist on both sets; which is none in this case. Is there an inclusive "and" ? – ravery Oct 24 '17 at 13:10
  • 1
    @ravery I put "and" just to explain what i'm looking for – HISI Oct 24 '17 at 14:06
  • 2
    @YassineSihi -- Well make note. Many new programmers stumble on this point until they can retrain their brain, because spoken language uses "and" inclusively but the logical "and" is exclusive in most programming languages. – ravery Oct 24 '17 at 15:35
10

You only need brace expansion for that

$ for n in {1..3} {200..203}; do echo $n; done
1
2
3
200
201
202
203

We can pass a list to for (for i in x y z; do stuff "$i"; done).

So here, braces { } get the shell to expand your sequences into a list. You only need put a space between them, since the shell splits lists of arguments on those.

|improve this answer|||||
  • Yep, braces . . . And you don't even need a loop for that ^_0 – Sergiy Kolodyazhnyy Oct 26 '17 at 19:49
  • @SergiyKolodyazhnyy I figure they don't actually just want to echo the numbers – Zanna Oct 27 '17 at 6:51
  • yep, if they want to some sort of action, like touch files, they can just do touch {1..15}.txt {20..25}.txt , no loop needed here. But of course if it's multiple actions on same number - OK, that could use a loop. – Sergiy Kolodyazhnyy Oct 27 '17 at 7:31
6

Alternatively we can use seq (print a sequence of numbers), here are two equivalent examples:

for i in `seq 1 3` `seq 101 103`; do echo $i; done
for i in $(seq 1 3) $(seq 101 103); do echo $i; done

If it is a script, for repetitive tasks, you can use functions:

#!/bin/bash
my_function() { echo "$1"; }
for i in {1..3}; do my_function "$i"; done
for i in {101..103}; do my_function "$i"; done
#!/bin/bash
my_function() { for i in `seq $1 $2`; do echo "$i"; done; }
my_function "1" "3"
my_function "101" "103"
|improve this answer|||||
4

Zanna's answer and pa4080's answer are both good and I'd probably go with one of them in most circumstances. Perhaps it goes without saying, but for the sake of completeness, I'll say it anyway: You can load each value into an array and then loop over the array. For example:

the_array=( 1 2 3 4 5 6 7 8 9 10 20 21 22 23 24 25 )
for i in "${the_array[@]}";
do
    echo $i
done
|improve this answer|||||
  • @SergiyKolodyazhnyy: Thanks the feedback. I'm old enough that that is how I was taught, and still usually do it on the rare occasion I'm writing a shell script. However, I've updated the answer to use an array. – GreenMatt Oct 26 '17 at 21:53
  • Very well ! Happy scripting ! – Sergiy Kolodyazhnyy Oct 26 '17 at 22:19
3

Looping without a loop

Zanna's answer is absolutely correct and well suited for bash, but we can improve that even more without utilizing a loop.

printf "%d\n"  {1..15} {20..25}

Behavior of printf is such that if the number of ARGUMENTS is greater than format controls in 'FORMAT STRING', then printf will split all ARGUMENTS into equal chunks and keep fitting them to the format string over and over until it runs out of ARGUMENTS list.

If we're striving for portability, we can utilize printf "%d\n" $(seq 1 15) $(seq 20 25) instead

Let's take this further and more fun. Say we want to perform an action rather than just printing numbers. For creating files out of that sequence of numbers, we could easily do touch {1..15}.txt {20..25}.txt. What if we want multiple things to occur? We could also do something like this:

$ printf "%d\n" {1..15} {20..25} | xargs -I % bash -c 'touch "$1.txt"; stat "$1.txt"' sh %

Or if we want to make it old-school style:

printf "%d\n" {1..15} {20..25} | while read -r line; do 
    touch "$line".txt;
    stat "$line".txt;
    rm "$line".txt; 
done

Portable but verbose alternative

If we want to make a script solution that works with shells that don't have brace expansion ( which is what {1..15} {20..25} relies on) , we can write a simple while loop:

#!/bin/sh
start=$1
jump=$2
new_start=$3
end=$4

i=$start
while [ $i -le $jump ]
do
    printf "%d\n" "$i"
    i=$((i+1))
    if [ $i -eq $jump ] && ! [ $i -eq $end ];then
        printf "%d\n" "$i"
        i=$new_start
        jump=$end
    fi
done

Of course this solution is more verbose, some things could be shortened, but it works. Tested with ksh, dash, mksh, and of course bash.


Bash C-style loop

But if we wanted to make a loop bash-specific ( for whatever reason, perhaps not just printing but also doing something with those numbers ), we can also do this (basically C-loop version of the portable solution) :

last=15; for (( i=1; i<=last;i++ )); do printf "%d\n" "$i"; [[ $i -eq $last ]] && !  [[ $i -eq 25 ]] && { i=19;last=25;} ;done

Or in more readable format:

last=15
for (( i=1; i<=last;i++ )); 
do 
    printf "%d\n" "$i"
    [[ $i -eq $last ]] && !  [[ $i -eq 25 ]] && { i=19;last=25;} 
done

Performance comparison of different looping approaches

bash-4.3$ time bash -c 'printf "%d\n" {0..50000}>/dev/null'

real    0m0.196s
user    0m0.124s
sys 0m0.028s
bash-4.3$ time bash -c 'for i in {1..50000}; do echo $i > /dev/null; done'

real    0m1.819s
user    0m1.328s
sys 0m0.476s
bash-4.3$ time bash -c ' i=0;while [ $i -le 50000 ]; do echo $i>/dev/null; i=$((i+1)); done'

real    0m3.069s
user    0m2.544s
sys 0m0.500s
bash-4.3$ time bash -c 'for i in $(seq 1 50000); do printf "%d\n" > /dev/null; done'

real    0m1.879s
user    0m1.344s
sys 0m0.520s

Non-shell alternative

Just because we can here's Python solution

$ python3 -c 'print("\n".join([str(i) for i in (*range(1,16),*range(20,26))]))'

Or with a bit of shell:

bash-4.3$ python3 << EOF
> for i in (*range(16),*range(20,26)):
>    print(i)
> EOF
|improve this answer|||||
  • 1
    I've just tested touch $(printf "%d\n" {1..15} {20..25}) :-) – pa4080 Oct 26 '17 at 22:18
  • 1
    @pa4080 actually for bash you don't even need $() there, just touch {1..15}.txt {20..25}.txt :) But of course we could use printf "%d\n {1..15} {20..25}` with xargs if we wanted to do more than just touch files. There's many ways to do things and it makes scripting so much fun ! – Sergiy Kolodyazhnyy Oct 27 '17 at 7:43

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.