How to easily escape a command-line filter

2019-07-23 04:24发布

问题:

I've seen discussions about performing pipes in Vim of the type

:%! cmd1 | cmd2 | ...

And it works with :w as well

:.w !cat | cat

And it works with :r

:r !echo hello | cat
:r !ls | grep .mp4

But the second I try something more ambitious, like, say, awk, it stops working:

:r !ls | awk '{printf "\"%s\"\n", $0}'
E499: Empty file name for '%' or '#', only works with ":p:h"

I have tried dozens of variations on the syntax to get it to work, including surrounding the entire command-line after ! with (), but it just refuses to work saying something about an Empty file name. That's what led me to search on Google and find out whether or not piping actually works in Vim. Thing is, it's not a problem with awk. Here's what happens with that same exact command-line on a bash shell:

$ ls | awk '{printf "\"%s\"\n", $0}'
"somefile1"
"somefile2"
"somefile3"
"somefile4"

It also works in sh (or dash on my system).

How do I make vim stop misinterpreting my input? Actually, I just figured out the solution to the specific problem that spurred this question on. You have to escape the % so it doesn't interpret it as the name of the opened file.

:r !ls | awk '{printf "\"\%s\"\n", $0}'
                         ^

However, this is still annoying behavior (and confusing IMO, Vim's escape for % looks just like awk's escapes; how is one supposed to know that doing that won't cause awk to output literally "%s"\n? It was a wild guess on my part).

I would like to know if there's a way to totally escape a filter (the part after the !) so that I don't have to slowly parse what I just wrote and special-case it just for Vim. Over-complicated escaping quickly gets unreadable and is one of my pet peeves when it comes to meta-programming.

Surely there must be a simple way to tell Vim to ignore characters like % et al, and pass them to the shell as-is, so that I can test my commands on the shell and copy/paste them into Vim without having to worry about manually escaping specific characters, because I can imagine that easily becoming a huge pain in the ass.

And actually, this has been a bit of an XY problem. I'll have to post the original problem in a different question, though, because the Y question is really interesting and I would like to know. I'll post a link to the X question in the comments.

回答1:

Your attempts at using shellescape() are close. What you're missing:

  • For :! (and :r !), you need to pass 1 for the {special} flag to it.
  • You can only escape a single command-line argument with it, not the entire command-line. As you only have critical characters in the last (awk script) argument, we only need it there:
:exe 'r ! ls | awk ' . shellescape('{printf "\"%s\"\n", $0}', 1)


标签: vim escaping