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?

3
  • @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, 2017 at 13:10
  • 1
    @ravery I put "and" just to explain what i'm looking for
    – HISI
    Oct 24, 2017 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, 2017 at 15:35

4 Answers 4

11

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.

3
  • Yep, braces . . . And you don't even need a loop for that ^_0 Oct 26, 2017 at 19:49
  • @SergiyKolodyazhnyy I figure they don't actually just want to echo the numbers
    – Zanna
    Oct 27, 2017 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. Oct 27, 2017 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"
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
2
  • @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, 2017 at 21:53
  • Very well ! Happy scripting ! Oct 26, 2017 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
2
  • 1
    I've just tested touch $(printf "%d\n" {1..15} {20..25}) :-)
    – pa4080
    Oct 26, 2017 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 ! Oct 27, 2017 at 7:43

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .