Bash: Brace expansion in scripts not working due to unwanted escaping
Asked Answered
D

9

10

I want to do something like this in a bash script. I'm using bash 4.1.10.

# rm -rf /some/path/{folder1,folder2,folder3}

Works nicely (and as expected) from the shell itself. It deletes the 3 desired folders leaving all others untouched.

When I put it into script something unwanted happens. For example, my script:

#!/bin/bash
set -x
VAR="folder1,folder2,folder3"
rm -rf /some/path/{$VAR}

When I execute this script, the folders are not deleted.

I think this is due to the fact that some unwanted quoting is occurring. Output from the script using #!/bin/bash -x:

rm -rf '/some/path/{folder1,folder2,folder3}'

which of course cannot succeed due to the ' marks.

How can I get this working within my script?

Dresden answered 1/7, 2011 at 14:6 Comment(2)
Do you absolutly want to use {}-expansion, or is an equivalent alternative ok?Averroism
sure.. I just have a list of subfolder names I want to remove in ONE concrete subshell in background -means more concrete: rm -rf /some/path/{folder1,folder2,folder3} & not a loop which runs over each subfolder doing this as this would increase fileserver loadDresden
G
12

According to the man page:

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.

So to get around this, add another level of expansion:

eval "rm -rf /some/path/{$VAR}"
Guidepost answered 1/7, 2011 at 14:13 Comment(3)
How to get around brace expansion, though?Protestation
function sort-note() { mv "$1"{,\;Sorted}; }. Eg, like in maildir, but for my own purposes?Protestation
At command line: mv foo.note{,\;Sorted}; ls foo.note* # foo.note;SortedProtestation
L
5

Since you're writing a script, there's no reason to write hard-to-maintain code using eval tricks

VAR="f1,f2,f3"
IFS=,
set -- $VAR
for f; do
  rm -r "/path/to/$f"
done

or

VAR=( f1 f2 f3 )
for f in "${VAR[@]}"; do 
  rm -r "/path/to/$f"
done
Logion answered 1/7, 2011 at 14:55 Comment(0)
G
2

No, it's due to the fact that brace expansion happens before parameter expansion. Find another way of doing this, such as with xargs.

xargs -d , -I {} rm -rf /some/path/{} <<< "$VAR"
Gilded answered 1/7, 2011 at 14:12 Comment(0)
A
2

If your code can be rearranged, you can use echo and command substitution in bash. Something like this:

#!/bin/bash
set -x
VAR=`echo /some/path/{folder1,folder2,folder3}`
rm -rf $VAR
Assembler answered 17/5, 2012 at 22:33 Comment(0)
O
1

You need to enable braceexpand flag:

#!/bin/bash
set -B
for i in /some/path/{folder1,folder2,folder3}
do
  rm -rf "$i"
done
Octavie answered 25/1, 2013 at 18:16 Comment(4)
DOesn't seem to work for me. (Note that the "/some/path/{folder1,folder2,folder3}" string is stored in a variable; if you enter the above literally in a shell it will work because the shell does the expansion, but it's of no use in a script.)Catholicity
But if you know the actual literal list of things you want to do brace expansion of, then it would work: for i in ${prefix}{fixed,list,of,literal,strings}${suffix} would loop over 5 strings, with ${prefix} and ${suffix} properly substituted.Catholicity
Fixed the answer. You need to set -B / braceexpand with set, or provide it as a bash parameter :)Octavie
No, still doesn't work. I don't think you understand the question: suppose you have a variable, like VAR="/tmp/{folder1,folder2,folder3}". Now you want to create (say) the corresponding directories, using $VAR. How do you do it? (If you don't have it in a var and can just type it directly, then everything is fine, as I said in my second comment above.)Catholicity
B
0
#!/bin/bash
set -x
VAR="folder1,folder2,folder3"
eval "rm -rf /some/path/{$VAR}"

Edit The remainder is just for info. I try to be informative, but not wordy :_)

Recent bashes have the globstar option. This might perhaps come in handy in the future

shopt -s globstar
rm -rfvi some/**/folder?
Beatabeaten answered 1/7, 2011 at 14:12 Comment(2)
i my concrete case I do not have folder1,folder2,folder3 but complete different names. Put for this special example this would have worked.Dresden
@Roland: I'm really mentioning globstar which I guess you didn't know yet. You did notice the answer (you know, the first part)?Beatabeaten
G
0

The problem is not that in script mode some unwanted quoting is happening but that you put the folder names into a variable and the variable content is expanded after the brace expansion is done.
If you really want to do it like this you have to use eval:

eval "rm -rf /some/path/{$VAR}"
Groovy answered 1/7, 2011 at 14:12 Comment(0)
C
0

Another trick you can use (instead of the dangerous eval) is just plain echo inside a subshell. This works, for instance:

paths=`echo /some/path/{folder1,folder2,folder3}`
echo rm -rf $paths

outputs:

rm -rf /some/path/folder1 /some/path/folder2 /some/path/folder3

as desired. (Remove the "echo" in the second line, to make it actually do the rm.)


The crucial point is that bash does brace expansion before parameter expansion, so you never want to put a comma-separated list (surrounded by braces or not) into a variable -- if you do, then you'll have to resort to eval. You can however put a list of space-separated strings into a variable, by having the brace expansion happen in a subshell before assignment.

Catholicity answered 8/2, 2013 at 5:42 Comment(0)
H
-1

replace {$VAR} by ${VAR} :-)

Hammered answered 1/7, 2011 at 14:13 Comment(1)
I want braces to be generated about the content of $VAR not brace expansion of $VARDresden

© 2022 - 2024 — McMap. All rights reserved.