Brace expansion with variable? [duplicate]
#!/bin/sh
for i in {1..5}
do
echo "Welcome"
done
Would work, displays Welcome 5 times.
#!/bin/sh
howmany=`grep -c $1 /root/file`
for i in {1..$howmany}
do
echo "Welcome"
done
Doesn't work! howmany
would equal 5 as that is what the output of grep -c
would display. $1 is parameter 1 which is specific when running the script.
Any ideas?
Solution 1:
Workarounds for not being able to use variables in a sequence brace expression:
-
If the intent is merely to iterate over numbers in a range - as in the OP's case - the best choice is not to use brace expansion, but instead use bash's C-style loop - see user000001's answer.
- If the specific numbers aren't important and you simply need to execute a loop body a specified number of times, Cole Tierney's answer is an option.
-
If use of brace expansion is desired nonetheless:
-
If you do NOT need the numbers in the list to have a prefix or postfix, use the
seq
utility with an unquoted command substitution (small caveat:seq
is NOT a POSIX utility, but it is widely available); e.g.-
echo $(seq 3)
->1 2 3
; start number1
implied-
echo $(seq -f '%02.f' 3)
->01 02 03
- zero-padded
-
-
echo $(seq 2 4)
->2 3 4
; explicit start and end numbers -
echo $(seq 1 2 5)
->1 3 5
; custom increment (the2
in the middle)
-
-
If you DO need the numbers in the list to have a prefix or postfix, you have several choices:
- Use the
seq
utility with its-f
option for providing aprintf
-style format string (as used above for zero-padding), or pure Bash workarounds based oneval
(extra care needed!) or building an array in a loop, all of which are detailed in this answer. - You could also consider implementing the functionality generically, such as by writing a custom shell function or a custom script with utilities such as
awk
orperl
.
- Use the
-
Example of safe use of eval
with variables driving a sequence brace expression:
The variables are validated beforehand, to make sure they contain decimal integers.
from=1 to=3 # sample values
# Ensure that $from and $to are decimal numbers and abort, if they are not.
(( 10#$from + 10#$to || 1 )) 2>/dev/null || { echo "Need decimal integers" >&2; exit 1; }
eval echo "A{$from..$to}" # -> 'A1 A2 A3'
General overview of brace expansion
The main purpose of brace expansion is to expand to a list of tokens with each token having an optional prefix and/or postfix; brace expansions must be unquoted and come in 2 flavors:
- a fixed series (list) of comma-separated strings - variables supported
- specifies and expands to a fixed number of tokens (2 or more); e.g.:
-
echo A{b,c,d}
->Ab Ac Ad
, i.e., 3 tokens, as implied by the number of args. -
echo {/,$HOME/}Library
e.g., ->/Library /User/jdoe/Library
- Variable references - and even globs - are supported, but note that they get expanded after brace expansion, in its result, in the course of normal evaluation.
-
a sequence expression (range) with
..
, typically numerical - variables NOT supported- expands to a variable number of tokens, driven by literal start and end points (for historical reasons, use of variables is NOT supported - see the comments on user000001's answer):
- [rare] strings: only single English letters allowed; e.g.
{a..c}
-
numbers: decimal integers only; e.g.,
{1..10}
,{10..1}
,{-1..2}
- example with prefix and postfix:
A{1..3}#
->A1# A2# A3#
-
broken example with variables:
{$from..$to} # !! FAILS
-$from
and$to
are interpreted as literals and therefore not recognized as either a single letter or a decimal integer - no brace expansion is performed (see below).- by contrast, using variables does work in
zsh
andksh
.
- by contrast, using variables does work in
- example with prefix and postfix:
-
bash 4+ adds two features:
- optional increment step value:
-
echo A{1..5..2}
->A1 A3 A5
- numbers incremented by 2
-
- ability to zero-pad:
-
echo A{001..003}
->A001 A002 A003
-
- optional increment step value:
- [rare] strings: only single English letters allowed; e.g.
- expands to a variable number of tokens, driven by literal start and end points (for historical reasons, use of variables is NOT supported - see the comments on user000001's answer):
-
An invalid brace expression is not expanded (treated like a regular unquoted string, with
{
and}
treated as literals):-
echo {}
->'{}'
- invalid as a brace expr.: at least 2,
-separated tokens needed- This allows the use of unquoted
{}
withfind
, for instance.
- This allows the use of unquoted
-
echo {1..$to}
->'{1..<value-of-$to>}'
- invalid as a brace expr. inbash
: variables not supported; however, valid inksh
andzsh
. - (
fish
, by contrast, expands any{...}
sequence; similarly,zsh
has optionBRACE_CCL
(OFF by default) for expanding individual characters inside{..}
, which effectively causes expansion of any nonempty{...}
sequence.)
-
Solution 2:
The brace expansion is evaluated before the variables are expanded. You need a c-style for loop instead:
for ((i=1;i<=howmany;i++))
do
echo "Welcome"
done
Solution 3:
create a sequence to control your loop
for i in $(seq 1 $howmany); do
echo "Welcome";
done