# HG changeset patch # User Steve Losh # Date 1339877120 14400 # Node ID 3e314e533b1d5f2c753b3011fba026d05f67643b # Parent 7dcc3e9f4ec393d5cad4272b547b1d8358a0bdbc Fix the shellescape/expand issue. Thanks @mrgrubb and @sedm0784. diff -r 7dcc3e9f4ec3 -r 3e314e533b1d chapters/32.markdown --- a/chapters/32.markdown Sat Jun 16 15:33:39 2012 -0400 +++ b/chapters/32.markdown Sat Jun 16 16:05:20 2012 -0400 @@ -113,9 +113,14 @@ :nnoremap g :grep -R '' . Most shells treat single-quoted text as (almost) literal, so our mapping is much -more robust now. However there's still one more problem with the search term! -Try the mapping on the word "that's". It won't work, because the single quote -inside the word interferes with the quotes in the grep command! +more robust now. + +Escaping Shell Command Arguments +-------------------------------- + +However there's still one more problem with the search term. Try the mapping on +the word "that's". It won't work, because the single quote inside the word +interferes with the quotes in the grep command! To get around this we can use Vim's `shellescape` function. Read `:help escape()` and `:help shellescape()` to see how it works (it's pretty simple). @@ -133,7 +138,51 @@ :::vim :nnoremap g :execute "grep -R " . shellescape("") . " ." -And now our mapping won't break if the word we're searching for happens to +Try it out by running it on a normal word like "foo". It will work properly. +Now try it out on a word with a quote in it, like "that's". It will not work! +What happened? + +The problem is that Vim performed the `shellescape()` call *before* it expanded +out special strings like `` in the command line. So Vim shell-escaped +the literal string `""` (which did nothing but add single quotes to it) +and then concatenated it with the strings of our `grep` command. + +You can see this by running the following command: + + :::vim + :echom shellescape("") + +Vim will output `''`. Note that those quotes are actually part of the +string -- Vim has prepared it for use as a shell command argument. + +To fix this we'll use the `expand()` function to force the expansion of +`` into the actual string *before* it gets passed to `shellescape`. + +Let's break this apart and see how it works, in steps. Put your cursor over +a word with q quote, like "that's", and run the following command: + + :::vim + :echom expand("") + +Vim outputs `that's` because `expand("")` will return the current word +under the cursor as a Vim string. Now let's add `shellescape` back in: + + :::vim + :echom shellescape(expand("")) + +This time Vim outputs `'that'\''s'`. If this looks a little funny, you haven't +had the pleasure of wrapping your brain around shell-quoting in all its insane +glory. For now, don't worry about it. Just trust the Vim has taken the string +from `expand` and escaped it properly. + +Now that we know how to get a fully-escaped version of the word under the +cursor, it's time to concatenate it into our mapping! Run the following +command: + + :::vim + :nnoremap g :execute "grep -R " . shellescape(expand("")) . " ." + +Try it out. Our mapping won't break if the word we're searching for happens to contain strange characters. The process of starting with a trivial bit of Vimscript and transforming it @@ -149,14 +198,14 @@ this command: :::vim - :nnoremap g :execute "grep! -R " . shellescape("") . " ." + :nnoremap g :execute "grep! -R " . shellescape(expand("")) . " ." Try it out again and nothing will seem to happen. Vim has filled the quickfix window with the results, but we haven't opened it yet. Run the following command: :::vim - :nnoremap g :execute "grep! -R " . shellescape("") . " .":copen + :nnoremap g :execute "grep! -R " . shellescape(expand("")) . " .":copen Now try the mapping and you'll see that Vim automatically opens the quickfix window with the search results. All we did was tack `:copen` onto the end @@ -166,7 +215,7 @@ searching. Run the following command: :::vim - :nnoremap g :silent execute "grep! -R " . shellescape("") . " .":copen + :nnoremap g :silent execute "grep! -R " . shellescape(expand("")) . " .":copen We're done, so try it out and admire your hard work! The `silent` command just runs the command that follows it while hiding any messages it would normally @@ -185,6 +234,8 @@ Set up mappings for `:cnext` and `:cprevious` to make it easier to quickly run through matches. +Read `:help expand`. + Read `:help copen`. Add a height to the `:copen` command in the mapping we created to make sure the