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?
Ask Ubuntu is a question and answer site for Ubuntu users and developers. It only takes a minute to sign up.
Sign up to join this communityI'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?
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.
echo
the numbers
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
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"
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
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
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
.
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
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
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
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