29

I want to use $var in a shell brace expansion with a range, in bash. Simply putting {$var1..$var2} doesn't work, so I went "lateral"...

The following works, but it's a bit kludgey.

# remove the split files
echo rm foo.{$ext0..$extN} rm-segments > rm-segments
source rm-segments

Is there a more "normal" way?

23

You may want to try :

eval rm foo.{$ext0..$extN}

Not sure whether this is the best answer, but it certainly is one.

  • Durrh! :( I couldn't see the obvious... Thanks :) – Peter.O Feb 21 '11 at 3:54
  • 3
    In bash, {} expansion happens before $ expansion, so I don't think there's any other way to do this other than using eval or some other trick to cause two passes through the expression. – mattdm Feb 21 '11 at 4:14
  • 6
    I just "got" it!... There is no brace expansion with {$one..$three}, because it is not a valid form of brace-expansion, which in this case expects integers... It only becomes a valid form after the $var expansion, which eval then passes through the same process to generate a normal brace sequence expansion... 1 2 3 QED ;) ... Summary: the simple presence of a brace pair does't trigger brace expansion... only a valid form triggers it. – Peter.O Feb 21 '11 at 6:49
  • @fred: Welcome :-) – asoundmove Feb 21 '11 at 21:48
  • When using a variable like a={0..9}; echo $a; there is no brace extension. Using eval , it works. So I think @mattdm's explanation is good. – dr0i May 22 '17 at 8:32
20

As you already realized, {1..3} expands to 1 2 3 but {foo..bar} or {$foo..$bar} don't trigger brace expansion, and the latter is subsequently expanded to replace $foo and $bar by their values.

A fallback on GNU (e.g. non-embedded Linux) is the seq command.

for x in `seq $ext0 $extN`; do rm foo.$x; done

Another possibility. if foo. contains no shell special character, is

rm `seq $ext0 $extN | sed 's/^/foo./'`

The simplest solution is to use zsh, where rm foo.{$ext0..$extN} does what you want.

  • Thanks for the options.. It's good to see them all grouped .. I'll stick with bash and eval... eval is simple enough now that I know what's going on behind the scenes; so it's no longer a problem. No need to change a good horse because of the rider :)... – Peter.O Feb 22 '11 at 4:36
1

While the other answers discuss using eval and seq, in bash, you can use a traditional C style for loop in an arithmetic context. The variables ext0 and extN are expanded inside the ((..)) causing the loop to run for the range defined.

for (( idx = ext0; idx <= extN; idx++ )); do
    [[ -f "$foo.$idx" ]] || { printf 'file does not exist \n' >&2 ; break ; }
    rm "$foo.$idx"
done

If you are looking for an optimal way and avoid multiple rm commands, you can use a temporary placeholder to store the filename results and call rm in one-shot.

 results=()
 for (( idx = ext0; idx <= extN; idx++ )); do
     [[ -f "$foo.$idx" ]] || { printf 'file does not exist \n' >&2 ; break ; }
     results+=( "$foo.$idx" )
 done

and now call the rm command on the expanded array

 rm -- "${results[@]}"
0
    function f_over_range {
        for i in $(eval echo {$1..$2}); do
            f $i
        done
    }

    function f {
        echo $1
    }

    f_over_range 0 5
    f_over_range 00 05

Notes:

  • Using eval exposes command injection security risk
  • Linux will print "00\n01\n02..etc", but OSX will print "1\n2\n...etc"
  • Using seq or C-style for loops won't match brace expansion's handling of leading zeros

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.