You can force another round of evaluation with eval
, but that's not actually necessary. (And eval
starts having serious problems the moment your file names contain special characters like $
.) The problem isn't with globbing, but with the tilde expansion.
Globbing happens after variable expansion, if the variable is unquoted, as here(*):
$ x="/tm*" ; echo $x
/tmp
So, in the same vein, this is similar to what you did, and works:
$ mkdir -p ~/public/foo/ ; touch ~/public/foo/x.launch
$ i="$HOME/public/*"; j="*.launch"; k="$i/$j"
$ echo $k
/home/foo/public/foo/x.launch
But with the tilde it doesn't:
$ i="~/public/*"; j="*.launch"; k="$i/$j"
$ echo $k
~/public/*/*.launch
This is clearly documented for Bash:
The order of expansions is: brace expansion; tilde expansion, parameter and variable expansion, ...
Tilde expansion happens before variable expansion so tildes inside variables are not expanded. The easy workaround is
to use $HOME
or the full path instead.
(* expanding globs from variables is usually not what you want)
Another thing:
When you loop over the patterns, as here:
exclude="foo *bar"
for j in $exclude ; do
...
note that as $exclude
is unquoted, it's both split, and also globbed at this point. So if the current directory contains something matching the pattern, it's expanded to that:
$ i="$HOME/public/foo"
$ exclude="*.launch"
$ touch $i/real.launch
$ for j in $exclude ; do # split and glob, no match
echo "$i"/$j ; done
/home/foo/public/foo/real.launch
$ touch ./hello.launch
$ for j in $exclude ; do # split and glob, matches in current dir!
echo "$i"/$j ; done
/home/foo/public/foo/hello.launch # not the expected result
To work around this, use an array variable instead of a splitted string:
$ exclude=("*.launch")
$ exclude+=("something else")
$ for j in "${exclude[@]}" ; do echo "$i"/$j ; done
/home/foo/public/foo/real.launch
/home/foo/public/foo/something else
As an added bonus, array entries can also contain whitespace without issues with splitting.
Something similar could be done with find -path
, if you don't mind what directory level the targeted files should be. E.g. to find any path ending in /e2e/*.js
:
$ dirs="$HOME/public $HOME/private"
$ pattern="*/e2e/*.js"
$ find $dirs -path "$pattern"
/home/foo/public/one/two/three/e2e/asdf.js
We have to use $HOME
instead of ~
for the same reason as before, and $dirs
needs to be unquoted on the find
command line so it gets split, but $pattern
should be quoted so it isn't accidentally expanded by the shell.
(I think you could play with -maxdepth
on GNU find to limit how deep the search goes, if you care, but that's a bit of a different issue.)
Best Answer
You can do this using the following
extglob
pattern inbash
:Enable
extglob
at first (if not enabled) by:Then you can do:
Example:
Shortened (thanks to chaos):