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 #8810)
This commit is contained in:
Jason Cox
2021-08-29 12:36:49 +02:00
committed by Bram Moolenaar
parent 9dcd349ca8
commit 6e82351130
4 changed files with 38 additions and 8 deletions

View File

@ -10111,6 +10111,10 @@ shellescape({string} [, {special}]) *shellescape()*
escaped. When 'shell' containing "csh" in the tail it's escaped. When 'shell' containing "csh" in the tail it's
escaped a second time. 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: > Example of use with a |:!| command: >
:exe '!dir ' . shellescape(expand('<cfile>'), 1) :exe '!dir ' . shellescape(expand('<cfile>'), 1)
< This results in a directory listing for the file under the < This results in a directory listing for the file under the

View File

@ -124,6 +124,15 @@ csh_like_shell(void)
return (strstr((char *)gettail(p_sh), "csh") != NULL); return (strstr((char *)gettail(p_sh), "csh") != NULL);
} }
/*
* 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(). * Escape "string" for use as a shell argument with system().
* This uses single quotes, except when we know we need to use double quotes * This uses single quotes, except when we know we need to use double quotes
@ -145,6 +154,7 @@ vim_strsave_shellescape(char_u *string, int do_special, int do_newline)
char_u *escaped_string; char_u *escaped_string;
int l; int l;
int csh_like; int csh_like;
int fish_like;
char_u *shname; char_u *shname;
int powershell; int powershell;
# ifdef MSWIN # ifdef MSWIN
@ -157,6 +167,10 @@ vim_strsave_shellescape(char_u *string, int do_special, int do_newline)
// Csh also needs to have "\n" escaped twice when do_special is set. // Csh also needs to have "\n" escaped twice when do_special is set.
csh_like = csh_like_shell(); 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 // PowerShell uses it's own version for quoting single quotes
shname = gettail(p_sh); shname = gettail(p_sh);
powershell = strstr((char *)shname, "pwsh") != NULL; powershell = strstr((char *)shname, "pwsh") != NULL;
@ -197,6 +211,8 @@ vim_strsave_shellescape(char_u *string, int do_special, int do_newline)
++length; // insert backslash ++length; // insert backslash
p += l - 1; p += l - 1;
} }
if (*p == '\\' && fish_like)
++length; // insert backslash
} }
// Allocate memory for the result and fill it. // Allocate memory for the result and fill it.
@ -261,6 +277,11 @@ vim_strsave_shellescape(char_u *string, int do_special, int do_newline)
*d++ = *p++; *d++ = *p++;
continue; continue;
} }
if (*p == '\\' && fish_like)
{
*d++ = '\\';
*d++ = *p++;
}
MB_COPY_CHAR(p, d); MB_COPY_CHAR(p, d);
} }

View File

@ -61,18 +61,21 @@ func Test_shell_options()
for e in shells for e in shells
exe 'set shell=' .. e[0] exe 'set shell=' .. e[0]
if e[0] =~# '.*csh$' || e[0] =~# '.*csh.exe$' if e[0] =~# '.*csh$' || e[0] =~# '.*csh.exe$'
let str1 = "'cmd \"arg1\" '\\''arg2'\\'' \\!%#'" let str1 = "'cmd \"arg1\" '\\''arg2'\\'' \\!%# \\'"
let str2 = "'cmd \"arg1\" '\\''arg2'\\'' \\\\!\\%\\#'" let str2 = "'cmd \"arg1\" '\\''arg2'\\'' \\\\!\\%\\# \\'"
elseif e[0] =~# '.*powershell$' || e[0] =~# '.*powershell.exe$' elseif e[0] =~# '.*powershell$' || e[0] =~# '.*powershell.exe$'
\ || e[0] =~# '.*pwsh$' || e[0] =~# '.*pwsh.exe$' \ || e[0] =~# '.*pwsh$' || e[0] =~# '.*pwsh.exe$'
let str1 = "'cmd \"arg1\" ''arg2'' !%#'" let str1 = "'cmd \"arg1\" ''arg2'' !%# \\'"
let str2 = "'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 else
let str1 = "'cmd \"arg1\" '\\''arg2'\\'' !%#'" let str1 = "'cmd \"arg1\" '\\''arg2'\\'' !%# \\'"
let str2 = "'cmd \"arg1\" '\\''arg2'\\'' \\!\\%\\#'" let str2 = "'cmd \"arg1\" '\\''arg2'\\'' \\!\\%\\# \\'"
endif endif
call assert_equal(str1, shellescape("cmd \"arg1\" 'arg2' !%#"), e[0]) call assert_equal(str1, shellescape("cmd \"arg1\" 'arg2' !%# \\"), e[0])
call assert_equal(str2, shellescape("cmd \"arg1\" 'arg2' !%#", 1), e[0]) call assert_equal(str2, shellescape("cmd \"arg1\" 'arg2' !%# \\", 1), e[0])
" Try running an external command with the shell. " Try running an external command with the shell.
if executable(e[0]) if executable(e[0])

View File

@ -755,6 +755,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
3385,
/**/ /**/
3384, 3384,
/**/ /**/