5

Bash 4 has a fantastic option called 'globstar' that emulates (i.e. was stolen from) zsh's ** syntax for globbing across multiple directories. However, it's somewhat crippled (for my usage, at least) by the fact that it always follows symlinks.

Is there a way to prevent ** from following symlinks? If not, is there any plan to add this feature to Bash in a future release? Until then (or if there is no such plan), can anyone suggest a decent (and convenient) workaround?

I think I've read that zsh's implementation is more flexible and doesn't have this problem, but unfortunately I can't just switch to zsh because we source a lot of bash scripts at my workplace, and I don't have the time or the know-how to convert all of them to zsh and keep them up-to-date. (I suppose it might be possible to start a Bash subshell, source the desired script, then somehow backport all the changed env vars, aliases, functions, etc into zsh, but I'm not sure how to do that, either.)

I know that I could write a function using find that would behave the way I want and then alias ** to this function, but then I couldn't do something like path/to/parent/**/child/paths and have it work correctly; the closest I could come would be something like $(** path/to/parent child/paths), where ** is aliased to a function that takes the parent path as a first argument and then includes --wholename */child/paths/* as an argument to the find command that it constructs and executes. This is awkward and ugly, and I'd really like something better. (In particular, I like that the path/**/path format easily allows the variations path/** and **/path easily and intuitively, whereas a function would turn this into ** path/ and ** '.' /path, respectively, the second of which is just terrible.)

Any thoughts?

  • Just to be clear, I don't mean "stolen" to be taken as a criticism here. – Kyle Strand Oct 18 '13 at 17:06
4

** doesn't follow symlinks since bash-4.3.

See CHANGES between bash-4.3-release and bash-4.3-rc2:

globstar (**) no longer traverses symbolic links that resolve to directories. This eliminates some duplicate entries.

3

There is no way to prevent ** from following symbolic links in bash, nor has a request to provide a way met any response.

If you feel like writing code, you could patch bash to introduce a new option that turns off symlink traversal with **. If you submit the patch to the bash maintainer, this would increase the chance of that feature being present one day. Nonetheless, at least in the meantime, you'd end up with having to deploy and maintain a local version of a critical system program, which is probably a bad idea.

There's no way to change the behavior of ** with an alias. If you use an intermediate array to store the result of the expansion, here's how you can use find instead:

find . … -print0 | {
  a=()
  while read -r -d "" line; do 
    a+=("$line")
  done
  frobnicate "${a[@]}"
}

Note that the whole part of the program that uses the output of find must be in the right-hand side of the pipeline, since bash executes the right-hand side of a pipeline in a subprocess.

If it's ok to exclude files whose name contains a newline character, it's easier:

set -f; IFS=
a=($(find . … ! -name $'*\n*' -print))
set +f; unset IFS
frobnicate "${a[@]}"

You could try running your existing scripts in zsh's ksh emulation mode. Zsh doesn't implement all of bash's features — almost all of bash's functionality exists in zsh but sometimes with an incompatible syntax. You could also try to run the scripts wih ksh93: versions since 93o+ support ** like in zsh, and most (but again, not all) of bash's features are from ksh so your scripts may be easy to port to ksh93. Switching to ksh has the additional benefit that it tends to be faster than either bash or zsh.

  • I'm not sure what you mean by "there's no way to change the behavior of ** with an alias," since you can indeed alias ** to a function name. Of course a function can't behave like the glob syntax, but the example usage I gave above works just fine. – Kyle Strand Oct 21 '13 at 16:48
  • @KyleStrand You can define an alias for **, but that doesn't change its behavior when it's used as a glob. – Gilles Oct 21 '13 at 17:03
  • I didn't think it would. We're saying the same thing. The last couple sentences of my original question are in fact pointing out the irritations of dealing with function syntax when what you really want is a glob. – Kyle Strand Oct 21 '13 at 17:06
  • In any case, thanks for the link to the existing feature request and the suggested workarounds, particularly the ksh emulation feature, which I previously didn't know about. I'll have to spend some more time fiddling with it, but it looks like that may be a viable solution. – Kyle Strand Oct 21 '13 at 23:28
-1

I've figured out a simpler workaround: just use zsh to evaluate these globs.

In my bash config scripts, I have the following:

function zeval () {
    echo $@ | zsh
}
alias z*='zeval echo'

Now, wherever I would use /path/with/**/glob in zsh, I can do the following in bash:

$(z* sandbox/rmall_gdsarc/**/*gds)

Since contexts in which aliases are expanded and contexts in which globbing is desired typically don't overlap, the alias z* shouldn't ever be ambiguous.

(The only context I can think of in which confusion between the alias z* and the Bash glob syntax would be if you were in a directory that was already on your path containing an executable script starting with 'z' and you wanted to execute it using a glob...which wouldn't make much sense anyway.)

  • Wonder why the downvote....I'm not bashing bash. – Kyle Strand Feb 4 '16 at 18:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.