Brace expansion with variable?
create a sequence to control your loop
for i in $(seq 1 $howmany); do
echo "Welcome";
done
How can I do brace expansion on variables?
See man bash
:
The order of expansions is: brace expansion, tilde expansion, parameter, variable and arithmetic expansion and command substitution (done in a left-to-right fashion), word splitting, and pathname expansion.
As you see, variable expansion happens later than brace expansion.
Fortunately, you don't need it at all: let the user specify the braced paths, let the shell expand them. You can then just
mv "$@"
If you need to separate the arguments, use an array and parameter expansion:
sources=("${@:1:$#-1}")
target=${@: -1}
mv "${sources[@]}" "$target"
how to use variables with brace expansion
Using variables with the sequence-expression form ({<numFrom>..<numTo>}
) of brace expansion only works in ksh
and zsh
, but, unfortunately, not in bash
(and (mostly) strictly POSIX-features-only shells such as dash
do not support brace expansion at all, so brace expansion should be avoided with /bin/sh
altogether).
Given your symptoms, I assume you're using bash
, where you can only use literals in sequence expressions (e.g., {1..3}
); from the manual (emphasis mine):
Brace expansion is performed before any other expansions, and any characters special to other expansions are preserved in the result.
In other words: at the time a brace expression is evaluated, variable references have not been expanded (resolved) yet; interpreting literals such as $var1
and $var2
as numbers in the context of a sequence expression therefore fails, so the brace expression is considered invalid and as not expanded.
Note, however, that the variable references are expanded, namely at a later stage of overall expansion; in the case at hand the literal result is the single word '{1..4}'
- an unexpanded brace expression with variable values expanded.
While the list form of brace expansion (e.g., {foo,bar)
) is expanded the same way, later variable expansion is not an issue there, because no interpretation of the list elements is needed up front; e.g. {$var1,$var2}
correctly results in the 2 words 1
and 4
.
As for why variables cannot be used in sequence expressions: historically, the list form of brace expansion came first, and when the sequence-expression form was later introduced, the order of expansions was already fixed.
For a general overview of brace expansion, see this answer.
Workarounds
Note: The workarounds focus on numerical sequence expressions, as in the question; the eval
-based workaround also demonstrates use of variables with the less common character sequence expressions, which produce ranges of English letters (e.g., {a..c}
to produce a b c
).
A seq
-based workaround is possible, as demonstrated in Jameson's answer.
A small caveat is that seq
is not a POSIX utility, but most modern Unix-like platforms have it.
To refine it a little, using seq
's -f
option to supply a printf
-style format string, and demonstrating two-digit zero-padding:
seq -f '%02.f.txt' $var1 $var2 | xargs ls # '%02.f'==zero-pad to 2 digits, no decimal places
Note that to make it fully robust - in case the resulting words contain spaces or tabs - you'd need to employ embedded quoting:
seq -f '"%02.f a.txt"' $var1 $var2 | xargs ls
ls
then sees 01 a.txt
, 02 a.txt
, ... with the argument boundaries correctly preserved.
If you want to robustly collect the resulting words in a Bash array first, e.g., ${words[@]}
:
IFS=$'\n' read -d '' -ra words < <(seq -f '%02.f.txt' $var1 $var2)
ls "${words[@]}"
The following are pure Bash workarounds:
A limited workaround using Bash features only is to use eval
:
var1=1 var2=4
# Safety check
(( 10#$var1 + 10#$var2 || 1 )) 2>/dev/null || { echo "Need decimal integers." >&2; exit 1; }
ls $(eval printf '%s\ ' "{$var1..$var2}.txt") # -> ls 1.txt 2.txt 3.txt 4.txt
You can apply a similar technique to a character sequence expression;
var1=a var2=c
# Safety check
[[ $var1 == [a-zA-Z] && $var2 == [a-zA-Z] ]] || { echo "Need single letters."; exit 1; }
ls $(eval printf '%s\ ' "{$var1..$var2}.txt") # -> ls a.txt b.txt c.txt
Note:
- A check is performed up front to ensure that
$var1
and$var2
contain decimal integers or single English letters, which then makes it safe to useeval
. Generally, usingeval
with unchecked input is a security risk and use ofeval
is therefore best avoided. - Given that output from
eval
must be passed unquoted tols
here, so that the shell splits the output into individual arguments through words-splitting, this only works if the resulting filenames contain no embedded spaces or other shell metacharacters.
A more robust, but more cumbersome pure Bash workaround to use an array to create the equivalent words:
var1=1 var2=4
# Emulate brace sequence expression using an array.
args=()
for (( i = var1; i <= var2; i++ )); do
args+=( "$i.txt" )
done
ls "${args[@]}"
- This approach bears no security risk and also works with resulting filenames with embedded shell metacharacters, such as spaces.
- Custom increments can be implemented by replacing
i++
with, e.g.,i+=2
to step in increments of 2. - Implementing zero-padding would require use of
printf
; e.g., as follows:args+=( "$(printf '%02d.txt' "$i")" ) # -> '01.txt', '02.txt', ...
How to store curly brackets in a Bash variable
The expression
ls {*.xml,*.txt}
results in Brace expansion and shell passes the expansion (if any) to ls
as arguments. Setting shopt -s nullglob
makes this expression evaluate to nothing when there are no matching files.
Double quoting the string suppresses the expansion and shell stores the literal contents in your variable name
(not sure if that is what you wanted). When you invoke ls
with $name
as the argument, shell does the variable expansion but no brace expansion is done.
As @Cyrus has mentioned, eval ls $name
will force brace expansion and you get the same result as that of ls {\*.xml,\*.txt}
.
Order of brace expansion and parameter expansion
from: https://www.gnu.org/software/bash/manual/html_node/Brace-Expansion.html
To avoid conflicts with parameter expansion, the string ‘${’ is not
considered eligible for brace expansion, and inhibits brace expansion
until the closing ‘}’.
That said, for echo $x{1..2}
, first the brace expansion takes place, and then the parameter expansion, so we have echo $x1 $x2
. For echo ${x{1..2}}
the brace expansion doesn't happen, because we are after the ${
and haven't reached the closing }
of the parameter expansion.
Regarding the bash manual part you have quoted, left-to-right order still exists for the expansions (with respect to allowed nested ones). Things get clearer if you format the list instead of using ,
and ;
:
- brace expansion
- In a left-to-right fashion:
tilde expansion, parameter and variable expansion, arithmetic expansion, and command substitution - word splitting
- filename expansion.
Bash variable expansion and brace expansion
You can try
A="a,b"
eval echo {$A}.txt
you get,
a.txt b.txt
Related Topics
Why Using Pipe for Sort (Linux Command) Is Slow
Oracle:Io Exception: the Network Adapter Could Not Establish the Connection
How to Make an "Alias" for a Long Path
How to Track Child Process Using Strace
Http Debugging Proxy for Linux and MAC
How to Recursively List All Directories at a Location, Breadth-First
How to Test If Your Linux Support Sse2
Why Is "Echo Foo | Read a ; Echo $A" Not Working as Expected
Resizing The Image with Padding Using Convert on Ubuntu
How to Gently Kill Firefox Process on Linux/Os X
How to Pass Command Line Parameters from a File
How to Find Words from One File in Another File
Symbolic Link: Find All Files That Link to This File
Linux Command History with Date and Time
Unix: Differencebetween Source and Export