You need to disable word splitting. Recall that this does not work:
$ p="a b.?" $ for f in ${p} ; do echo "|$f|" ; done |a| |b.?|
This, however, does:
$ ( IFS=; for f in ${p} ; do echo "|$f|" ; done ) |a b.1| |a b.2| |a b.3|
IFS is an "Internal Field Separator" wrapper. It is usually set to a space, a tab, and a new line character. It is used to break words after expanding a variable. Setting IFS to start stopping word splitting and thus allows you to work with glob.
Array Example
The same applies to array examples:
$ declare -a A=($p) ; for f in "${A[@]}" ; do echo "|$f|" ; done |a| |b.?| $ ( IFS=; declare -a A=($p) ; for f in "${A[@]}" ; do echo "|$f|" ; done ) |a b.1| |a b.2| |a b.3|
Make sure that the IFS returns to its normal value
In the examples above, I put the IFS assignment inside a subshell. Although not necessary, the advantage of this is that IFS automatically reverts to its previous value as soon as the subshell ends. If subshells are not suitable for your application, here is another approach:
$ oldIFS=$IFS; IFS=; for f in ${p} ; do echo "|$f|" ; done; IFS=$oldIFS |a b.1| |a b.2| |a b.3|
Matching Shell Symbols
Suppose we have files that have a literal * in their names:
$ touch ab.{1,2,3} 'a*b'.{1,2,3} $ ls a*b.1 ab.1 a*b.2 ab.2 a*b.3 ab.3
And suppose we want to match this star. Since we want our heads to be addressed literally, we must avoid this:
$ p='a\*b.?' $ ( IFS=; for f in ${p} ; do echo "|$f|" ; done ) |a*b.1| |a*b.2| |a*b.3|
Since ? not escaped, it is considered as a wildcard. Since * escaped, it matches only a literal * .