tags:

views:

3615

answers:

4

When the following two lines of code are executed in a bash script, "ls" complains that the files don't exist:

dirs=/content/{dev01,dev02}
ls -l $dirs

When I run the script with the -x option, it appears to be passing the variable within single quotes (which would prevent globbing):

+ dirs=/content/{dev01,dev01}
+ ls -l '/content/{dev01,dev01}'
ls: /content/{dev01,dev01}: No such file or directory

If I execute the "ls" command from my interactive shell (sans quotes), it returns the two directories.

I've been reading through the Bash Reference Manual (v 3.2) and can't see any reason for filename globbing to not take place (I'm not passing -f to the shell), or anything that I can set to ensure that globbing happens.

Any ideas?

A: 
ls `echo $dirs`

works under cygwin.

Zsolt Botykai
Doesn't work in GNU bash, version 3.2.17(1)-release (i386-apple-darwin9.0)
EightyEight
It's 3.2.39(20) under cygwin.
Zsolt Botykai
+5  A: 

I think it is the order of expansions:

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 if your variable is substituted, brace expansion doesn't take place anymore. This works for me:

eval ls $dirs

Be very careful with eval. It will execute the stuff verbatimly. So if dirs contains f{m,k}t*; some_command, some_command will be executed after the ls finished. It will execute the string you give to eval in the current shell. It will pass /content/dev01 /content/dev02 to ls, whether they exist or not. Putting * after the stuff makes it a pathname-expansion, and it will omit non-existing paths:

dirs=/content/{dev01,dev02}*

I'm not 100% sure about this, but it makes sense to me.

Johannes Schaub - litb
Brace expansion happens prior to variable expansion
Petesh
yes, that is the reason. when the variable is expanded, brace expansion doesn't take place anymore, since it already took place before the variable was expanded.
Johannes Schaub - litb
if that were the case, I would have expected "dirs" to contain a list of words resulting from the brace expansion, which may or may not apply to the actual filesystem - but that doesn't appear to happen
kdgregory
pathname expansion will happen. so a=a* ls $a will expand to files starting with "a". but brace expansion won't happen.
Johannes Schaub - litb
+1  A: 

Here is an excellent discussion of what you are trying to do.

The short answer is that you want an array:

dirs=(/content/{dev01,dev01})

But what you do with the results can get more complex than what you were aiming for I think.

feoh
I actually saw that thread, but it didn't seem to help me. The problem comes with the following "ls ${dirs}", which seems to get only the first entry in the array. I also tried "set ${dirs};ls $*" but that was the same result.
kdgregory
you have to do ls "${dirs[@]}" . but it will expand the names at assignment time to dirs. so dirs will actually contain the filenames, not only the pattern
Johannes Schaub - litb
+1  A: 

This isn't filename globbing, this is brace expansion. The difference is subtle, but it exists - in filename globbing you would only receive existing files as a result, while in brace expansion you can generate any kind of string.

http://www.gnu.org/software/bash/manual/bashref.html#Brace-Expansion

http://www.gnu.org/software/bash/manual/bashref.html#Filename-Expansion

Now, this is what worked for me:

#!/bin/sh
dirs=`echo ./{dev01,dev02}`
ls $dirs
Yoni Roit
well then you could do dirs=( ./{dev01,dev02} ); what feoh says. but it will expand at the time of the assignment. which may not what he want
Johannes Schaub - litb