vim-patch:9.0.1946: filename expansion using ** in bash may fail

Problem:  filename expansion using ** in bash may fail
Solution: Try to enable the globstar setting

Starting with bash 4.0 it supports extended globbing using the globstar
shell option. This makes matching recursively below a certain directory
using the ** pattern work as expected nowadays.  However, we need to
explicitly enable this using the 'shopt -s globstar' bash command.

So let's check the bash environment variable $BASH_VERSINFO (which is
supported since bash 3.0 and conditionally enable the globstar option,
if the major version is at least 4. For older bashs, this at least
shouldn't cause errors (unless one is using really ancient bash 2.X or
something).

closes: vim/vim#13002
closes: vim/vim#13144

9eb1ce5315

Co-authored-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
zeertzjq
2023-09-28 05:09:21 +08:00
parent 7f58b2bb6a
commit 92e40f8d18
3 changed files with 44 additions and 6 deletions

View File

@@ -356,7 +356,9 @@ as a wildcard when "[" is in the 'isfname' option. A simple way to avoid this
is to use "path\[[]abc]", this matches the file "path\[abc]". is to use "path\[[]abc]", this matches the file "path\[abc]".
*starstar-wildcard* *starstar-wildcard*
Expanding "**" is possible on Unix, Win32, macOS and a few other systems. Expanding "**" is possible on Unix, Win32, macOS and a few other systems (but
it may depend on your 'shell' setting. It's known to work correctly for zsh; for
bash this requires at least bash version >= 4.X).
This allows searching a directory tree. This goes up to 100 directories deep. This allows searching a directory tree. This goes up to 100 directories deep.
Note there are some commands where this works slightly differently, see Note there are some commands where this works slightly differently, see
|file-searching|. |file-searching|.

View File

@@ -134,6 +134,8 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in
#define STYLE_VIMGLOB 2 // use "vimglob", for Posix sh #define STYLE_VIMGLOB 2 // use "vimglob", for Posix sh
#define STYLE_PRINT 3 // use "print -N", for zsh #define STYLE_PRINT 3 // use "print -N", for zsh
#define STYLE_BT 4 // `cmd` expansion, execute the pattern directly #define STYLE_BT 4 // `cmd` expansion, execute the pattern directly
#define STYLE_GLOBSTAR 5 // use extended shell glob for bash (this uses extended
// globbing functionality with globstar, needs bash > 4)
int shell_style = STYLE_ECHO; int shell_style = STYLE_ECHO;
int check_spaces; int check_spaces;
static bool did_find_nul = false; static bool did_find_nul = false;
@@ -141,6 +143,9 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in
// vimglob() function to define for Posix shell // vimglob() function to define for Posix shell
static char *sh_vimglob_func = static char *sh_vimglob_func =
"vimglob() { while [ $# -ge 1 ]; do echo \"$1\"; shift; done }; vimglob >"; "vimglob() { while [ $# -ge 1 ]; do echo \"$1\"; shift; done }; vimglob >";
// vimglob() function with globstar setting enabled, only for bash >= 4.X
static char *sh_globstar_opt =
"[[ ${BASH_VERSINFO[0]} -ge 4 ]] && shopt -s globstar; ";
bool is_fish_shell = bool is_fish_shell =
#if defined(UNIX) #if defined(UNIX)
@@ -190,6 +195,8 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in
// If we use *zsh, "print -N" will work better than "glob". // If we use *zsh, "print -N" will work better than "glob".
// STYLE_VIMGLOB: NL separated // STYLE_VIMGLOB: NL separated
// If we use *sh*, we define "vimglob()". // If we use *sh*, we define "vimglob()".
// STYLE_GLOBSTAR: NL separated
// If we use *bash*, we define "vimglob() and enable globstar option".
// STYLE_ECHO: space separated. // STYLE_ECHO: space separated.
// A shell we don't know, stay safe and use "echo". // A shell we don't know, stay safe and use "echo".
if (num_pat == 1 && *pat[0] == '`' if (num_pat == 1 && *pat[0] == '`'
@@ -203,10 +210,13 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in
shell_style = STYLE_PRINT; shell_style = STYLE_PRINT;
} }
} }
if (shell_style == STYLE_ECHO if (shell_style == STYLE_ECHO) {
&& strstr(path_tail(p_sh), "sh") != NULL) { if (strstr(path_tail(p_sh), "bash") != NULL) {
shell_style = STYLE_GLOBSTAR;
} else if (strstr(path_tail(p_sh), "sh") != NULL) {
shell_style = STYLE_VIMGLOB; shell_style = STYLE_VIMGLOB;
} }
}
// Compute the length of the command. We need 2 extra bytes: for the // Compute the length of the command. We need 2 extra bytes: for the
// optional '&' and for the NUL. // optional '&' and for the NUL.
@@ -214,6 +224,8 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in
len = strlen(tempname) + 29; len = strlen(tempname) + 29;
if (shell_style == STYLE_VIMGLOB) { if (shell_style == STYLE_VIMGLOB) {
len += strlen(sh_vimglob_func); len += strlen(sh_vimglob_func);
} else if (shell_style == STYLE_GLOBSTAR) {
len += strlen(sh_vimglob_func) + strlen(sh_globstar_opt);
} }
for (i = 0; i < num_pat; i++) { for (i = 0; i < num_pat; i++) {
@@ -281,6 +293,9 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in
STRCAT(command, "print -N >"); STRCAT(command, "print -N >");
} else if (shell_style == STYLE_VIMGLOB) { } else if (shell_style == STYLE_VIMGLOB) {
STRCAT(command, sh_vimglob_func); STRCAT(command, sh_vimglob_func);
} else if (shell_style == STYLE_GLOBSTAR) {
STRCAT(command, sh_globstar_opt);
STRCAT(command, sh_vimglob_func);
} else { } else {
STRCAT(command, "echo >"); STRCAT(command, "echo >");
} }
@@ -430,7 +445,9 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in
p = skipwhite(p); // skip to next entry p = skipwhite(p); // skip to next entry
} }
// file names are separated with NL // file names are separated with NL
} else if (shell_style == STYLE_BT || shell_style == STYLE_VIMGLOB) { } else if (shell_style == STYLE_BT
|| shell_style == STYLE_VIMGLOB
|| shell_style == STYLE_GLOBSTAR) {
buffer[len] = NUL; // make sure the buffer ends in NUL buffer[len] = NUL; // make sure the buffer ends in NUL
p = buffer; p = buffer;
for (i = 0; *p != NUL; i++) { // count number of entries for (i = 0; *p != NUL; i++) { // count number of entries
@@ -496,7 +513,7 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in
(*file)[i] = p; (*file)[i] = p;
// Space or NL separates // Space or NL separates
if (shell_style == STYLE_ECHO || shell_style == STYLE_BT if (shell_style == STYLE_ECHO || shell_style == STYLE_BT
|| shell_style == STYLE_VIMGLOB) { || shell_style == STYLE_VIMGLOB || shell_style == STYLE_GLOBSTAR) {
while (!(shell_style == STYLE_ECHO && *p == ' ') while (!(shell_style == STYLE_ECHO && *p == ' ')
&& *p != '\n' && *p != NUL) { && *p != '\n' && *p != NUL) {
p++; p++;

View File

@@ -3281,4 +3281,23 @@ func Test_fullcommand()
call assert_equal('', fullcommand(10)) call assert_equal('', fullcommand(10))
endfunc endfunc
" Test for glob() with shell special patterns
func Test_glob_extended_bash()
CheckExecutable bash
let _shell = &shell
set shell=bash
call mkdir('Xtestglob/foo/bar/src', 'p')
call writefile([], 'Xtestglob/foo/bar/src/foo.sh')
call writefile([], 'Xtestglob/foo/bar/src/foo.h')
call writefile([], 'Xtestglob/foo/bar/src/foo.cpp')
" Sort output of glob() otherwise we end up with different
" ordering depending on whether file system is case-sensitive.
let expected = ['Xtestglob/foo/bar/src/foo.cpp', 'Xtestglob/foo/bar/src/foo.h']
call assert_equal(expected, sort(glob('Xtestglob/**/foo.{h,cpp}', 0, 1)))
call delete('Xtestglob', 'rf')
let &shell=_shell
endfunc
" vim: shiftwidth=2 sts=2 expandtab " vim: shiftwidth=2 sts=2 expandtab