How to assign a glob expression to a variable in a Bash script?

BashGlob

Bash Problem Overview


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.

Bash Solutions


Solution 1 - Bash

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.

Solution 2 - Bash

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.

Solution 3 - Bash

For folks (like me) finding this through Google, @Peter and @feoh's answers are the general solution to "How to glob variables in bash script".

list_of_results=(pattern)

will save existing filenames matching pattern into the array list_of_results. Each element of list_of_results will hold one filename, spaces and all.

You can access each result as "${list_of_results[<index>]}" for <index> starting from 0. You can get the entire list, properly quoted, as "${list_of_results[@]}".

Solution 4 - Bash

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

Solution 5 - Bash

Since you want to glob files, you shouldn't use brace expansions. Using brace expansion in this case is an antipattern and definitely the wrong tool for the job.

What you want is extended globbing:

shopt -s extglob # likely already set in interactive shells

dirs=/content/@(dev01|dev02)
ls $dirs

Solution 6 - Bash

I suspect that what you need is an array, but that will restrict you to newer bashes. It is saver than using eval.

dirs=( /"content with spaces"/{dev01,dev02} )

dirs=( /content/{dev01,dev02} )
ls -l "${dirs[@]}"

/content/{dev01,dev02}

will expand to:

"/content/dev01" "/content/dev02"

The existence of those directories is irrelevant to the expansion.

It becomes unpredictable when you assign a variable to a brace expansion.

dirs=/content/{dev01,dev02}

may turn into

"/content/dev01"

or

"/content/dev01 /content/dev02"

or

"/content/dev01" "/content/dev02"

or

"/content/{dev01,dev02}"

If you quote the braces in any way they will not expand, so the result will contain the braces and be mostly meaningless.

Solution 7 - Bash

ls `echo $dirs` 

works under cygwin.

Solution 8 - Bash

The issue which no-one has addressed is that the variable assignment makes the difference.

dirs=/content/{dev01,dev02}

expands differently than

echo /content/{dev01,dev02}

The question is how to assign the results of the expansion to dirs

See: https://stackoverflow.com/questions/8700530/how-to-use-bash-substitution-in-a-variable-declaration#comment10833052_8700530

Solution 9 - Bash

I came here with a similar question. I don't like the use of eval if there's an alternative. And I wanted something that works for any sh, not just bash.

dirs="dev01 dev02"
for dir in $dirs
do
  ls -l content/"$dir"
done

Note that $dirs is unquoted so that the for loop sees the space.

This answer doesn't work if there is an unescaped space inside the directory name, but there are probably similar solutions that can handle that situation.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionkdgregoryView Question on Stackoverflow
Solution 1 - BashJohannes Schaub - litbView Answer on Stackoverflow
Solution 2 - BashfeohView Answer on Stackoverflow
Solution 3 - BashcxwView Answer on Stackoverflow
Solution 4 - BashYoni RoitView Answer on Stackoverflow
Solution 5 - Bashgniourf_gniourfView Answer on Stackoverflow
Solution 6 - BashPeterView Answer on Stackoverflow
Solution 7 - BashZsolt BotykaiView Answer on Stackoverflow
Solution 8 - BashSam LiddicottView Answer on Stackoverflow
Solution 9 - BashcjfpView Answer on Stackoverflow