Monday, January 29, 2007

Handling range parameters in Bash

I've come across a few occasions where I wanted to specify a "range parameter" for Bash scripts. Like "1..4" meaning do for 1,2,3 and 4.

Here's a simple trick that uses the (relatively obscure) variable expansion and substring replacement capabilities of the shell.
#!/bin/bash
v_range="1..3" # or you could have taken it as a script parameter
v_start=${v_range%%.*} # chomp everything from the left-most matching "."
v_end=${v_range##*.} # chomp everything up to the right-most matching "."

The repeated %% and ## basically mean you will get a "greedy" match, so you can say "1..4" or "1....5"; it doesn't matter how many repeats you have of the range delimiter. Of course, you can choose other delimiters such as a hyphen, as in "5-10", if you wish.

Now that you have extracted the start and end indices, you can then loop or whatever to your hearts content:
for ((a=v_start; a <= v_end ; a++))
do
echo "Looping with a=$a"
done

For more information on variable expansion, see 9.3 in the Advanced Bash Scripting Guide.

Postscript 5-Feb-2008:
We live and learn! Hat tip to buford for alerting me to the seq utility, which simplifies the iteration over a range, as in:
for a in `seq 1 10`
do
echo "Looping with a=$a"
done

You still need to determine the start and end values of the range, which is the whole point of the variable expansions approach posted here.

9 comments:

buford said...

for f in `seq 1 10`; do echo $f; done

Anonymous said...

It's not a builtin, it's a program:
type seq:
seq is hashed (/usr/bin/seq)

It's part of coreutils.

jadu saikia said...

$(jot - 1 16 2)

can be a replacement of the following two:

for((i=1;i<=16;i+=2))
$(seq 1 2 16)

** jot prints sequential or random
data

More of jot : http://unstableme.blogspot.com/2007/12/jot-print-sequential-or-random-data.html

// Jadu

Paul said...

Thanks Jadu, jot is yet another little utility I'd never heard of before!

Like your blog too..

Anonymous said...

for x in {1..4}; do echo $x; done

Paul said...

@anonymous:

for x in {1..4}; do echo $x; done

.. works OK with literals, but how do you substitute the range with a variable?

e.g.
v_range=1..4
for x in {${v_range}}; do echo $x; done
# doesn't work, nor does:
v_start=1
v_end=4
for x in {${v_start}..${v_end}}; do echo $x; done
# nor does:
for x in {v_start..v_end}; do echo $x; done

Szilard said...

a=1; b=10; for i in `seq $a $b`; do echo $i; done

works for me

Szilard said...

$a=1
$b=10
for i in 'seq $a $b`;do echo $i; done

Works for me.

Paul said...

@Szilard good thought, although I think it still requires some parsing in order to deal with parameters supplied like "1..4" or "1-3", which was the original problem I was facing