changeset 33374:62a34e280593 v9.0.1946

patch 9.0.1946: filename expansion using ** in bash may fail Commit: https://github.com/vim/vim/commit/9eb1ce531527a7177d16373b0f8689bbcd3d5f73 Author: Christian Brabandt <cb@256bit.org> Date: Wed Sep 27 19:08:25 2023 +0200 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>
author Christian Brabandt <cb@256bit.org>
date Wed, 27 Sep 2023 19:15:06 +0200
parents 25583f388016
children ae0a6df8e181
files runtime/doc/editing.txt src/os_unix.c src/testdir/test_functions.vim src/version.c
diffstat 4 files changed, 51 insertions(+), 9 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/editing.txt
+++ b/runtime/doc/editing.txt
@@ -1,4 +1,4 @@
-*editing.txt*   For Vim version 9.0.  Last change: 2023 Apr 23
+*editing.txt*   For Vim version 9.0.  Last change: 2023 Sep 22
 
 
 		  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -385,7 +385,9 @@ as a wildcard when "[" is in the 'isfnam
 is to use "path\[[]abc]", this matches the file "path\[abc]".
 
 					*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.
 Note there are some commands where this works slightly differently, see
 |file-searching|.
--- a/src/os_unix.c
+++ b/src/os_unix.c
@@ -6701,14 +6701,17 @@ mch_expand_wildcards(
 #define STYLE_GLOB	1	// use "glob", for csh
 #define STYLE_VIMGLOB	2	// use "vimglob", for Posix sh
 #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 using globstar, needs bash > 4)
     int		shell_style = STYLE_ECHO;
     int		check_spaces;
     static int	did_find_nul = FALSE;
     int		ampersand = FALSE;
 		// vimglob() function to define for Posix shell
     static char *sh_vimglob_func = "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; ";
 
     *num_file = 0;	// default: no files found
     *file = NULL;
@@ -6755,6 +6758,8 @@ mch_expand_wildcards(
      *	    If we use *zsh, "print -N" will work better than "glob".
      * STYLE_VIMGLOB:	NL separated
      *	    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.
      *	    A shell we don't know, stay safe and use "echo".
      */
@@ -6769,9 +6774,13 @@ mch_expand_wildcards(
 	else if (STRCMP(p_sh + len - 3, "zsh") == 0)
 	    shell_style = STYLE_PRINT;
     }
-    if (shell_style == STYLE_ECHO && strstr((char *)gettail(p_sh),
-								"sh") != NULL)
-	shell_style = STYLE_VIMGLOB;
+    if (shell_style == STYLE_ECHO)
+    {
+       if (strstr((char *)gettail(p_sh), "bash") != NULL)
+	    shell_style = STYLE_GLOBSTAR;
+       else if (strstr((char *)gettail(p_sh), "sh") != NULL)
+	    shell_style = STYLE_VIMGLOB;
+    }
 
     // Compute the length of the command.  We need 2 extra bytes: for the
     // optional '&' and for the NUL.
@@ -6779,6 +6788,9 @@ mch_expand_wildcards(
     len = STRLEN(tempname) + 29;
     if (shell_style == STYLE_VIMGLOB)
 	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)
     {
@@ -6847,6 +6859,11 @@ mch_expand_wildcards(
 	    STRCAT(command, "print -N >");
 	else if (shell_style == STYLE_VIMGLOB)
 	    STRCAT(command, sh_vimglob_func);
+	else if (shell_style == STYLE_GLOBSTAR)
+	{
+	    STRCAT(command, sh_globstar_opt);
+	    STRCAT(command, sh_vimglob_func);
+	}
 	else
 	    STRCAT(command, "echo >");
     }
@@ -7031,7 +7048,9 @@ mch_expand_wildcards(
 	}
     }
     // 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
 	p = buffer;
@@ -7112,7 +7131,7 @@ mch_expand_wildcards(
 	(*file)[i] = p;
 	// Space or NL separates
 	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 == ' ')
 						   && *p != '\n' && *p != NUL)
--- a/src/testdir/test_functions.vim
+++ b/src/testdir/test_functions.vim
@@ -3625,4 +3625,23 @@ func Test_fullcommand()
   call assert_equal('', fullcommand(10))
 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
--- a/src/version.c
+++ b/src/version.c
@@ -700,6 +700,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1946,
+/**/
     1945,
 /**/
     1944,