3

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"
Braiam
  • 35,991
41754
  • 95
  • 2
    @jordanm - This seems slightly different than the duplicate you identified. The dup just shows brace expansions in a for loop, this is asking how to do arthmetic expansion inside a brace expansion. – slm Aug 27 '14 at 15:57

2 Answers2

4

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
choroba
  • 47,233
2
eval 'for i in {'"$((1 + 1))"'..5}; do echo $i; done'

OUTPUT

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
mikeserv
  • 58,310