Skip to content

Commit 9eb1ce5

Browse files
committed
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: #13002 closes: #13144 Signed-off-by: Christian Brabandt <cb@256bit.org>
1 parent 2dede3d commit 9eb1ce5

4 files changed

Lines changed: 51 additions & 9 deletions

File tree

runtime/doc/editing.txt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
*editing.txt* For Vim version 9.0. Last change: 2023 Apr 23
1+
*editing.txt* For Vim version 9.0. Last change: 2023 Sep 22
22

33

44
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -385,7 +385,9 @@ as a wildcard when "[" is in the 'isfname' option. A simple way to avoid this
385385
is to use "path\[[]abc]", this matches the file "path\[abc]".
386386

387387
*starstar-wildcard*
388-
Expanding "**" is possible on Unix, Win32, macOS and a few other systems.
388+
Expanding "**" is possible on Unix, Win32, macOS and a few other systems (but
389+
it may depend on your 'shell' setting. It's known to work correctly for zsh; for
390+
bash this requires at least bash version >= 4.X).
389391
This allows searching a directory tree. This goes up to 100 directories deep.
390392
Note there are some commands where this works slightly differently, see
391393
|file-searching|.

src/os_unix.c

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6701,14 +6701,17 @@ mch_expand_wildcards(
67016701
#define STYLE_GLOB 1 // use "glob", for csh
67026702
#define STYLE_VIMGLOB 2 // use "vimglob", for Posix sh
67036703
#define STYLE_PRINT 3 // use "print -N", for zsh
6704-
#define STYLE_BT 4 // `cmd` expansion, execute the pattern
6705-
// directly
6704+
#define STYLE_BT 4 // `cmd` expansion, execute the pattern directly
6705+
#define STYLE_GLOBSTAR 5 // use extended shell glob for bash (this uses extended
6706+
// globbing functionality using globstar, needs bash > 4)
67066707
int shell_style = STYLE_ECHO;
67076708
int check_spaces;
67086709
static int did_find_nul = FALSE;
67096710
int ampersand = FALSE;
67106711
// vimglob() function to define for Posix shell
67116712
static char *sh_vimglob_func = "vimglob() { while [ $# -ge 1 ]; do echo \"$1\"; shift; done }; vimglob >";
6713+
// vimglob() function with globstar setting enabled, only for bash >= 4.X
6714+
static char *sh_globstar_opt = "[[ ${BASH_VERSINFO[0]} -ge 4 ]] && shopt -s globstar; ";
67126715

67136716
*num_file = 0; // default: no files found
67146717
*file = NULL;
@@ -6755,6 +6758,8 @@ mch_expand_wildcards(
67556758
* If we use *zsh, "print -N" will work better than "glob".
67566759
* STYLE_VIMGLOB: NL separated
67576760
* If we use *sh*, we define "vimglob()".
6761+
* STYLE_GLOBSTAR: NL separated
6762+
* If we use *bash*, we define "vimglob() and enable globstar option".
67586763
* STYLE_ECHO: space separated.
67596764
* A shell we don't know, stay safe and use "echo".
67606765
*/
@@ -6769,16 +6774,23 @@ mch_expand_wildcards(
67696774
else if (STRCMP(p_sh + len - 3, "zsh") == 0)
67706775
shell_style = STYLE_PRINT;
67716776
}
6772-
if (shell_style == STYLE_ECHO && strstr((char *)gettail(p_sh),
6773-
"sh") != NULL)
6774-
shell_style = STYLE_VIMGLOB;
6777+
if (shell_style == STYLE_ECHO)
6778+
{
6779+
if (strstr((char *)gettail(p_sh), "bash") != NULL)
6780+
shell_style = STYLE_GLOBSTAR;
6781+
else if (strstr((char *)gettail(p_sh), "sh") != NULL)
6782+
shell_style = STYLE_VIMGLOB;
6783+
}
67756784

67766785
// Compute the length of the command. We need 2 extra bytes: for the
67776786
// optional '&' and for the NUL.
67786787
// Worst case: "unset nonomatch; print -N >" plus two is 29
67796788
len = STRLEN(tempname) + 29;
67806789
if (shell_style == STYLE_VIMGLOB)
67816790
len += STRLEN(sh_vimglob_func);
6791+
else if (shell_style == STYLE_GLOBSTAR)
6792+
len += STRLEN(sh_vimglob_func)
6793+
+ STRLEN(sh_globstar_opt);
67826794

67836795
for (i = 0; i < num_pat; ++i)
67846796
{
@@ -6847,6 +6859,11 @@ mch_expand_wildcards(
68476859
STRCAT(command, "print -N >");
68486860
else if (shell_style == STYLE_VIMGLOB)
68496861
STRCAT(command, sh_vimglob_func);
6862+
else if (shell_style == STYLE_GLOBSTAR)
6863+
{
6864+
STRCAT(command, sh_globstar_opt);
6865+
STRCAT(command, sh_vimglob_func);
6866+
}
68506867
else
68516868
STRCAT(command, "echo >");
68526869
}
@@ -7031,7 +7048,9 @@ mch_expand_wildcards(
70317048
}
70327049
}
70337050
// file names are separated with NL
7034-
else if (shell_style == STYLE_BT || shell_style == STYLE_VIMGLOB)
7051+
else if (shell_style == STYLE_BT ||
7052+
shell_style == STYLE_VIMGLOB ||
7053+
shell_style == STYLE_GLOBSTAR)
70357054
{
70367055
buffer[len] = NUL; // make sure the buffer ends in NUL
70377056
p = buffer;
@@ -7112,7 +7131,7 @@ mch_expand_wildcards(
71127131
(*file)[i] = p;
71137132
// Space or NL separates
71147133
if (shell_style == STYLE_ECHO || shell_style == STYLE_BT
7115-
|| shell_style == STYLE_VIMGLOB)
7134+
|| shell_style == STYLE_VIMGLOB || shell_style == STYLE_GLOBSTAR)
71167135
{
71177136
while (!(shell_style == STYLE_ECHO && *p == ' ')
71187137
&& *p != '\n' && *p != NUL)

src/testdir/test_functions.vim

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3625,4 +3625,23 @@ func Test_fullcommand()
36253625
call assert_equal('', fullcommand(10))
36263626
endfunc
36273627

3628+
" Test for glob() with shell special patterns
3629+
func Test_glob_extended_bash()
3630+
CheckExecutable bash
3631+
let _shell = &shell
3632+
set shell=bash
3633+
3634+
call mkdir('Xtestglob/foo/bar/src', 'p')
3635+
call writefile([], 'Xtestglob/foo/bar/src/foo.sh')
3636+
call writefile([], 'Xtestglob/foo/bar/src/foo.h')
3637+
call writefile([], 'Xtestglob/foo/bar/src/foo.cpp')
3638+
3639+
" Sort output of glob() otherwise we end up with different
3640+
" ordering depending on whether file system is case-sensitive.
3641+
let expected = ['Xtestglob/foo/bar/src/foo.cpp', 'Xtestglob/foo/bar/src/foo.h']
3642+
call assert_equal(expected, sort(glob('Xtestglob/**/foo.{h,cpp}', 0, 1)))
3643+
call delete('Xtestglob', 'rf')
3644+
let &shell=_shell
3645+
endfunc
3646+
36283647
" vim: shiftwidth=2 sts=2 expandtab

src/version.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,8 @@ static char *(features[]) =
699699

700700
static int included_patches[] =
701701
{ /* Add new patch number below this line */
702+
/**/
703+
1946,
702704
/**/
703705
1945,
704706
/**/

0 commit comments

Comments
 (0)