52

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?

5 Answers 5

49

You may want to try :

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

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

6
  • 1
    Durrh! :( I couldn't see the obvious... Thanks :)
    – Peter.O
    Feb 21, 2011 at 3:54
  • 5
    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, 2011 at 4:14
  • 9
    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, 2011 at 6:49
  • 1
    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, 2017 at 8:32
  • 1
    You can also use bash -c "..." instead of eval to ensure that brace-expansion actually works shell-agnostic. e.g. Dash does not support it.
    – Morty
    Jan 22, 2021 at 9:35
29

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.

2
  • 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, 2011 at 4:36
  • A small suggestion: seq -w $ext0 $extN may be closer to what you want. Without -w, the inputs 001 003 will produce 1 2 3, whereas with it, you'll get 001 002 003. I generally want the latter. Apr 11, 2020 at 14:56
3

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 %s does not exist" "$foo.$idx" >&2 ; continue ; }
    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 %s does not exist" "$foo.$idx" >&2 ; continue ; }
     results+=( "$foo.$idx" )
 done

and now call the rm command on the expanded array

 rm -- "${results[@]}"
2
    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
1
  • I think this is the best answer to the question which brought me here: "Why can't a variable be in between the range-making curly braces?" I think this might have been the OP's question as well, given the quote, "I had proposed that the problem was to 'use a variable inside mkdir {1 .. $variable}'". I always get tripped up by how (actually, mostly by when) the shell does the brace expansion. I've just got to keep telling myself, "eval, echo, range-with-variables, all inside a dollar paren ($(...))". +1 Sep 22, 2022 at 19:09
0

I have the following command that finds the total number of files in my root directory:

command 01:

 ls -dq *dog* | wc -l

I also have a command to create multiple folders using a numeric sequence:

command 02:

mkdir -p {0..3}

I know in the root directory there are 3 files that have the string "DOG" in your name.

But I would like to use the 01 command in other root folders and that counted how many files have "DOG" in your name, store the result in variable result and used this variable in place of "3" as seen in command 02.

Below all my possible attempts without having been able to create subfolders 1, 2 and 3:

result=$ls -dq *dog* | wc -l) mkdir {0..$result}

result=$(ls -dq *dog* | wc -l) | mkdir {0..$result}

ls -dq *dog* | wc -l | xargs I{} mkdir {0..{}}





ls -dq *dog* | wc -l | while read file; do mkdir -p {1.."$file"}; done

But for any attempt above I only have two results: or is created a folder called {0..3} or nothing happens besides receiving a printing of the result of

 ls -dq *dog* | wc -l

Solved so bellow:

Looking at this answer it was possible to build a model capable of getting the desired result:

eval "mkdir -p {1..`ls -dq *dog* | wc -l`}"

While strictly is not the most appropriate answer because I had proposed that the problem was to "use a variable inside mkdir {1 .. $variable}"

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .