I can iterate over multiple ranges / sequences / numbers with constructions like:
for i in $(seq 1 3) 5 $(seq 7 9) 11; do echo $i; done
for i in {1..3} 5 {7..9} 11; do echo $i; done
But how to achieve the same result if ranges / numbers specified in var:
var="{1..3} 5 {7..9} 11"
I found that this can be done with eval
and seq
:
var="seq 1 3; echo 5; seq 7 9; echo 11"; for i in $(eval "$var"); do echo $i; done
Here shown that eval echo
allows to use ranges in vars like (much nicer than the previous one):
var="{1..3} 5 {7..9} 11"; for i in $(eval echo "$var"); do echo $i; done
Is it possible to achieve the same result somehow without using eval
? Or using eval
in this particular case is OK?
Update
Since there are additional overhead with parsing ranges / numbers defined in plain string variable and since I have control over forming such parameter it will be also worth considering solutions for input array var defined like the following:
ranges=( '1 3' 5 '7 9' 11 )
Or even more straightforward and simpler for implementation (but a bit redundant in expression) with using also ranges instead of numbers:
ranges=( '1 3' '5 5' '7 9' '11 11' )
In case of using array with ranges as input string this question becomes pretty simple and boils down to double loop over array with ranges and over each range of this array.
With specifying ranges as array there will be no need of using eval
or parsing string with ranges to array (that have the same but hidden danger of eval
).
You could use:
var="{1..3} 5 {7..9} 11"
declare -a "array=($var)"
for i in "${array[@]}"; do
echo $i
done
1
2
3
5
7
8
9
11
eval
:var='{1..3} $(uptime) 6 7'
echo "$var"
{1..3} $(uptime) 6 7
declare -a "array=($var)"
for i in "${array[@]}"; do
echo $i
done
1
2
3
16:36:29
up
22
days,
4:54,
23
users,
load
average:
2.48,
1.89,
1.83
6
7
Note that using double quotes:
var='{1..3} "$(uptime)" 6 7'
Will produce more readable:
1
2
3
16:41:03 up 22 days, 4:59, 23 users, load average: 1.47, 2.09, 1.98
6
7
About:
Or using eval in this particular case is OK?
This is your own responsibility! If you are knowing what you do, you are confident on where come $var
content, then it could be ok.
eval
, you have do loops.If you want to avoid eval
, you could use a function like:
readRange() { # Usage: readRange "$var" <arrayName>
local -a arin
local elem
local -i iter start end
local -n arout=$2
arout=()
read -ra arin <<< "$1"
for elem in "${arin[@]}"; do
case $elem in
'{'[0-9]*'..'*[0-9]'}' )
IFS='.{}' read -r _ start _ end <<< "$elem"
for ((iter=start; iter<=end; iter++)); do
arout+=($iter)
done
;;
*[^0-9]* )
echo ERROR 1>&2
return -1
;;
*)
arout+=($elem)
;;
esac
done
}
var="{1..3} 5 {7..9} 11"
readRange "$var" resultArray
for i in "${resultArray[@]}"; do
echo "$i"
done
1
2
3
5
7
8
9
11
seq
:readRange() { # Usage: readRange "$var" <arrayName>
local -a arin
local elem start end
local -n arout=$2
arout=()
read -ra arin <<< "$1"
for elem in "${arin[@]}"; do
read -r start end <<< ${elem//[^0-9]/ }
if [[ -n $end ]]; then
arout+=($(seq $start $end))
else
arout+=($start)
fi
done
}
var="{1..3} 5 {7..9} 11"
readRange "$var" resultArray
for i in "${resultArray[@]}"; do
echo "$i"
done
1
2
3
5
7
8
9
11