Vim – How to escape filename containing single and double quotes mix

filenamesquotingvim

Let's say i create a filename with this:

xb@dnxb:/tmp/test$ touch '"i'"'"'m noob.mp4"'
xb@dnxb:/tmp/test$ ls -1
"i'm noob.mp4"
xb@dnxb:/tmp/test$ 

Then vim . to go inside Netrw directory listing.

" ============================================================================
" Netrw Directory Listing                                        (netrw v156)
"   /tmp/test
"   Sorted by      name
"   Sort sequence: [\/]$,\<core\%(\.\d\+\)\=\>,\.h$,\.c$,\.cpp$,\~\=\*$,*,\.o$,\.obj$,\.info$,\.swp$,\.bak$,\~$
"   Quick Help: <F1>:help  -:go up dir  D:delete  R:rename  s:sort-by  x:special
" ==============================================================================
../
./
"i'm noob.mp4"

Then press Enter to view the file. Type:

:!ls -l %

It will shows error:

xb@dnxb:/tmp/test$ vim .

ls: cannot access '/tmp/test/i'\''m noob.mp4': No such file or directory

shell returned 2

Press ENTER or type command to continue

I also tried:

[1] :!ls -l '%':

Press ENTER or type command to continue
/bin/bash: -c: line 0: unexpected EOF while looking for matching `"'
/bin/bash: -c: line 1: syntax error: unexpected end of file

shell returned 1

Press ENTER or type command to continue

[2] :!ls -l "%":

Press ENTER or type command to continue
/bin/bash: -c: line 0: unexpected EOF while looking for matching `''
/bin/bash: -c: line 1: syntax error: unexpected end of file

shell returned 1

Press ENTER or type command to continue

[3] :!ls -l expand("%"):

/bin/bash: -c: line 0: syntax error near unexpected token `('
/bin/bash: -c: line 0: `ls -l expand(""i'm noob.mp4"")'

shell returned 1

Press ENTER or type command to continue

[4] !ls -l shellescape("%"):

/bin/bash: -c: line 0: syntax error near unexpected token `('
/bin/bash: -c: line 0: `ls -l shellescape("/tmp/test/"i'm noob.mp4"")'

shell returned 1

Press ENTER or type command to continue

[5] !ls -l shellescape(expand("%")):

/bin/bash: -c: line 0: syntax error near unexpected token `('
/bin/bash: -c: line 0: `ls -l shellescape(expand("/tmp/test/"i'm noob.mp4""))'

shell returned 1

Press ENTER or type command to continue

My ultimate goal is perform rsync by Ctrl+c, e.g:

nnoremap <C-c> :!eval `ssh-agent -s`; ssh-add; rsync -azvb --no-t % xiaobai@127.0.0.1:/home/xiaobai/storage/

My platform is Kali Linux's vim.gtk3, bash. Fedora's vim and gvim also have the same problem.

What's the correct syntax to escape filename containing single and double quotes in vim ?

[UPDATE]

exec '!ls -l' shellescape(expand('%')) can work, but stil i can't figure out how to make rsync above work. I have no idea where should i put quotes for this more complex command rsync.

Best Answer

From :help filename-modifiers:

The file name modifiers can be used after "%", "#", "#n", "<cfile>", "<sfile>",
"<afile>" or "<abuf>".  ...

...
    :s?pat?sub?
            Substitute the first occurrence of "pat" with "sub".  This
            works like the |:s| command.  "pat" is a regular expression.
            Any character can be used for '?', but it must not occur in
            "pat" or "sub".
            After this, the previous modifiers can be used again.  For
            example ":p", to make a full path after the substitution.
    :gs?pat?sub?
            Substitute all occurrences of "path" with "sub".  Otherwise
            this works like ":s".

So rather than just handling double quotes or single quotes, let's just backslash escape everything unusual:

:!ls -l %:gs/[^0-9a-zA-Z_-]/\\&/

Works perfectly with the test filename you provided.

To use an absolute path, which you may want for rsync, you can add :p at the end:

:!ls -l %:gs/[^0-9a-zA-Z_-]/\\&/:p

Actually, it also works just fine if you backslash-escape literally every character, and it's shorter to type:

:!ls -l %:gs/./\\&/:p

So, in your rsync command, instead of %, use %:gs/./\\&/:p.

Related Question