In this Ask Ubuntu post, I used globstar to locate a file when PATH
is unset:
$ shopt -s globstar; for v in /**/vim; do [[ -x $v && -f $v ]] && echo "$v"; done
/etc/alternatives/vim
/usr/bin/vim
/usr/bin/X11/vim
Now that I think about it, that output seems a bit odd. /usr/bin/X11
is a symlink to /usr/bin
:
$ readlink /usr/bin/X11
.
So, there's an infinite recursion of X11
s there, but only the first of them turned up in the output. Weirdly, just a /usr/**
doesn't descend into X11
at all:
$ printf "%s\n" /usr/bin/** | grep X11
/usr/bin/X11
How can the first and the last outputs be reconciled?
From comments:
I'm using Bash version 4.4.18(1) on Ubuntu 16.04.
Best Answer
tl;dr - Bash expansion is complicated to prevent infinite symlink loops (in
bash >= 4.3
), and you and I both misinterpreted what it was doing in the commands you postedI assume you have
bash >= 4.3
as I cannot reproduce what you describe inbash 4.2.46
, it loops until it hits a recursion limit (as expected).Stared at this for a while and set up a test directory to play in that imitaed your situation. The crux of this is how the bash expansion happens in each of your examples. The expansion behaves differently based on whether or not it is followed by a
/
, and there's just some cognitive dissonance on that point for us primates when looking at examples like this.From the documentation for bash shopt:
To illustrate here's my test setup:
resulting in this directory structure:
This duplicates your findings in my test directory:
In the first example, the glob expands to
(test/nested/looper, test/nested, test)
, stopping atlooper
without following the link because the glob was followed by a/
We then append
/sneaky
to that, resulting in the set(test/nested/looper/sneaky, test/nested/sneaky, test/sneaky)
.In the second example, the glob expands to
(test/nested/looper, test/nested/sneaky, test/nested, test/sneaky, test)
(which you can verify by removing the| grep sneaky
)Again, this expansion does not follow the
looper
link, but in this case we do not append/sneaky
to it, thus dropping../test/nested/looper/sneaky
from our results.On the other hand, we continue to get
../test/nested/sneaky
and../test/sneaky
because the glob grabs files as well when it is not followed by a/