Can peoples combine arithmetic expansion and brace expansion?
$ for i in {$((1 + 1))..5}; do echo $i; done;
{2..5}
$ echo "bash laughs at me"
Can peoples combine arithmetic expansion and brace expansion?
$ for i in {$((1 + 1))..5}; do echo $i; done;
{2..5}
$ echo "bash laughs at me"
See man bash
for explanation:
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.
Brace expansion happens before arithmetic expansion, so you can't combine them in the way you tried.
Use seq
instead:
for i in $(seq $((a+4)) 12) ; do echo $i ; done
eval 'for i in {'"$((1 + 1))"'..5}; do echo $i; done'
2
3
4
5
In general if you want the shell to do something before it actually does, you have to give the shell a second look at it. This is eval
's function - it twice-evaluates a command. The shell parses its evaluation of shell expansions - which generally does not happen.
Consider this:
v='"quotes in here"'; printf %s\\n $v
"quotes
in
here"
You see the quotes are interpreted by the shell's parser, which handles the input before expansions are made, and so the quotes within $v
have no meaning there - there is no parser to understand them. But if I do:
v='"quotes in here"'; eval "printf '%s\\n' $v"
quotes in here
The output is suddenly different. The difference is made by the parser - it's the bit that decides what bits of its input are commands and why. It's the part that works with ;${}()||''""&& while for if
all of that kind of stuff. That doesn't mean that the {}
braces back there are equivalent to the ones you're asking about - as the other answer has already shown, those are apparently handled separately as a bash
extension. In any case, generally you need some form of second evaluation to handle this.
It is this that makes eval
dangerous. When you "${expansion}"
quote an expansion you mark it for the parser - you delimit it. The shell knows that, whatever happens, that is but one item - likely an argument - in a command. And even when you don't; the shell will not accept an $expansion
that falls outside a ;
simple command boundary - because that simple command is also already delimited. But - as it does with eval
- if the shell were to come back and look at that expansion once more, it might find a command it can run - even several - and this is how eval
works.
So when using eval
you have to be very careful that you do not unexpectedly twice evaluate a shell token or any expansion that might contain one - and you only evaluate on the first go-round the part that the shell does not do soon enough otherwise. Best practice is only to singly evaluate any one part of the command, but at different times, as is done below.
Here's that first eval
string again:
eval \ #inital command
'for i in {'\ #hard-quoted - not expanded or executed
"$((1 + 1))"\ #double-quoted - expanded and delimited
'..5}; do echo $i; done' #hard-quoted - not expanded or executed
The comments above discuss the actions taken during eval
's inital pass - there is one more to go. In this case, you want 1+1
. So we first expand that and nothing else. The worst that we can get out of that is 2
. This is not a shell parser token and it is safe to evaluate - and is even double-quoted. In fact, if there is any real valid use of eval
it is arithmetic. Everything else is hard-quoted - there is no action taken and the strings are simply concatenated and the quotes removed, as would happen with any other hard-quoted string.
But when it comes back the second time - after the quotes are removed from the first pass - the shell finds:
for i in {2..5}; do echo $i; done
And it does it.
There are other ways to go about this:
bash -c "for i in {$((1+1))..5}; do echo \$i; done"
^that works.
And:
. /dev/fd/0 <<CMD
for i in {$((1+1))..5}; do echo \$i; done
CMD
^that works. In every case the logistics are the same - you evaluate the variable before you evaluate the bash
brace expansion.
I've never had a lot of use for the brace expansion myself, though. I usually do like:
until [ $((i=$i+1)) -gt 5 ]
do printf %d\\n $i
done