# HG changeset patch # User Bram Moolenaar # Date 1630233905 -7200 # Node ID 000b37efd5faa7c833e5631617973058c7456ef8 # Parent c7845af77c6559624f9569060685dd2c275712a8 patch 8.2.3385: escaping for fish shell does not work properly Commit: https://github.com/vim/vim/commit/6e82351130ddb8d13cf3748b47f07cae77886fc7 Author: Jason Cox Date: Sun Aug 29 12:36:49 2021 +0200 patch 8.2.3385: escaping for fish shell does not work properly Problem: Escaping for fish shell does not work properly. Solution: Insert a backslash before a backslash. (Jason Cox, closes https://github.com/vim/vim/issues/8810) diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -10111,6 +10111,10 @@ shellescape({string} [, {special}]) *s escaped. When 'shell' containing "csh" in the tail it's escaped a second time. + The "\" character will be escaped when 'shell' contains "fish" + in the tail. That is because for fish "\" is used as an escape + character inside single quotes. + Example of use with a |:!| command: > :exe '!dir ' . shellescape(expand(''), 1) < This results in a directory listing for the file under the diff --git a/src/strings.c b/src/strings.c --- a/src/strings.c +++ b/src/strings.c @@ -125,6 +125,15 @@ csh_like_shell(void) } /* + * Return TRUE when 'shell' has "fish" in the tail. + */ + int +fish_like_shell(void) +{ + return (strstr((char *)gettail(p_sh), "fish") != NULL); +} + +/* * Escape "string" for use as a shell argument with system(). * This uses single quotes, except when we know we need to use double quotes * (MS-DOS and MS-Windows not using PowerShell and without 'shellslash' set). @@ -145,6 +154,7 @@ vim_strsave_shellescape(char_u *string, char_u *escaped_string; int l; int csh_like; + int fish_like; char_u *shname; int powershell; # ifdef MSWIN @@ -157,6 +167,10 @@ vim_strsave_shellescape(char_u *string, // Csh also needs to have "\n" escaped twice when do_special is set. csh_like = csh_like_shell(); + // Fish shell uses '\' as an escape character within single quotes, so '\' + // itself must be escaped to get a literal '\'. + fish_like = fish_like_shell(); + // PowerShell uses it's own version for quoting single quotes shname = gettail(p_sh); powershell = strstr((char *)shname, "pwsh") != NULL; @@ -197,6 +211,8 @@ vim_strsave_shellescape(char_u *string, ++length; // insert backslash p += l - 1; } + if (*p == '\\' && fish_like) + ++length; // insert backslash } // Allocate memory for the result and fill it. @@ -261,6 +277,11 @@ vim_strsave_shellescape(char_u *string, *d++ = *p++; continue; } + if (*p == '\\' && fish_like) + { + *d++ = '\\'; + *d++ = *p++; + } MB_COPY_CHAR(p, d); } diff --git a/src/testdir/test_shell.vim b/src/testdir/test_shell.vim --- a/src/testdir/test_shell.vim +++ b/src/testdir/test_shell.vim @@ -61,18 +61,21 @@ func Test_shell_options() for e in shells exe 'set shell=' .. e[0] if e[0] =~# '.*csh$' || e[0] =~# '.*csh.exe$' - let str1 = "'cmd \"arg1\" '\\''arg2'\\'' \\!%#'" - let str2 = "'cmd \"arg1\" '\\''arg2'\\'' \\\\!\\%\\#'" + let str1 = "'cmd \"arg1\" '\\''arg2'\\'' \\!%# \\'" + let str2 = "'cmd \"arg1\" '\\''arg2'\\'' \\\\!\\%\\# \\'" elseif e[0] =~# '.*powershell$' || e[0] =~# '.*powershell.exe$' \ || e[0] =~# '.*pwsh$' || e[0] =~# '.*pwsh.exe$' - let str1 = "'cmd \"arg1\" ''arg2'' !%#'" - let str2 = "'cmd \"arg1\" ''arg2'' \\!\\%\\#'" + let str1 = "'cmd \"arg1\" ''arg2'' !%# \\'" + let str2 = "'cmd \"arg1\" ''arg2'' \\!\\%\\# \\'" + elseif e[0] =~# '.*fish$' || e[0] =~# '.*fish.exe$' + let str1 = "'cmd \"arg1\" '\\''arg2'\\'' !%# \\\\'" + let str2 = "'cmd \"arg1\" '\\''arg2'\\'' \\!\\%\\# \\\\'" else - let str1 = "'cmd \"arg1\" '\\''arg2'\\'' !%#'" - let str2 = "'cmd \"arg1\" '\\''arg2'\\'' \\!\\%\\#'" + let str1 = "'cmd \"arg1\" '\\''arg2'\\'' !%# \\'" + let str2 = "'cmd \"arg1\" '\\''arg2'\\'' \\!\\%\\# \\'" endif - call assert_equal(str1, shellescape("cmd \"arg1\" 'arg2' !%#"), e[0]) - call assert_equal(str2, shellescape("cmd \"arg1\" 'arg2' !%#", 1), e[0]) + call assert_equal(str1, shellescape("cmd \"arg1\" 'arg2' !%# \\"), e[0]) + call assert_equal(str2, shellescape("cmd \"arg1\" 'arg2' !%# \\", 1), e[0]) " Try running an external command with the shell. if executable(e[0]) diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -756,6 +756,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 3385, +/**/ 3384, /**/ 3383,