11

From help :help backtick-expansion:

On Unix and a few other systems you can also use backticks for the file name
argument, for example:
    :next `find . -name ver\\*.c -print`
    :view `ls -t *.patch  \| head -n1`
The backslashes before the star are required to prevent the shell from
expanding "ver*.c" prior to execution of the find program.  The backslash
before the shell pipe symbol "|" prevents Vim from parsing it as command
termination.

If I type the command taken from the help, I get an error:

:next `find . -name ver\\*.c -print
E79: Cannot expand wildcards  

Why does the example from the help use two backslashes instead of one, and why does it not work?

The following work:

  • If I remove one of the two backslashes which protect the star from being expanded by the shell before the find program:

    :next `find . -name ver\*.c -print`
    
  • If I remove both of backslashes and put single quotes around the pattern ver*.c:

    :next `find . -name 'ver*.c' -print`
    

Up to this point, the rule seems to be:
if your shell command contains a star and you don't want the shell to expand it before the command, put one backslash in front of it or put single quotes around the pattern.

But the help gives another example:

:view `ls -t *.patch  \| head -n1`

This command works without any modification, no need of single quotes, no need of backslash.
I suppose the reason why it works is because the ls command (contrary to the -name argument of the find command) accept multiple file arguments and sees no problem with the shell expanding *.patch.

Now, let's say I want to look for all the files with the extension .conf inside the /etc folder and pipes the output of find to grep to get only the matches containing the string input.
In the shell, from any working directory, I would type:

find /etc -name '*.conf' | grep input

And it would work.

In vim, I will type the same command putting backticks around it, and a backslash in front of the pipe symbol to prevent vim from interpreting it as a command termination :

:next `find /etc -name '*.conf' \| grep input`

And it works.

Now, if I type the same command without the pipe and the grep input, I get an error:

:next `find /etc -name '*.conf'`
E79: Cannot expand wildcards

Why is there an error in this case even though I protected the star with single quotes?
And why is there an error now, but not just before with the pipe and the grep input?

To try understanding, I've come up with a simpler command:

find . -name '*.conf'

It looks for all the files with the extension .conf in the working directory. The command works in the shell.

To test it in vim, I typed: :next `find . -name '*.conf'`
And it works. In this case the dot stands for my current working directory as displayed by the Ex command :pwd which is my home directory /home/username since I launched the vim session from it.

Why does it work when I ask to search in the current working directory whereas it doesn't work when I ask to search in an arbitrary folder such as /etc?

Now, if I change my working directory from /home/username to /etc with the vim Ex command :cd /etc and retry the same command as before, again it errors out:

:next `find . -name '*.conf'`
E79: Cannot expand wildcards

Why does the same command work when I'm in my home folder but not when I'm in /etc ?

I'm sure there's some logic, but I can't find one.

What is the general and correct syntax to populate the arglist with an arbitrary shell command (containing a star, a pipe, searching in any directory from any working directory) ?

I'm using vim version 7.4.942 and zsh is my default shell. I tested these commands with a minimum of initializations (vim -u NORC -N) from bash as well as from zsh.

Do I need to configure vim to call bash and not zsh ?

  • I’m wondering if you tried to start vim with all those files passed as arguments? I mean something like: vim $(find . -name ver*.c) – Vlad GURDIGA Dec 24 '15 at 10:50
  • @VladGURDIGA I've just tried and they all work as expected from the shell: vim $(find . -name ver\*.c -print), vim $(ls -t *.patch | head -n1), vim $(find /etc -name '*.conf' | grep input), vim $(find /etc -name '*.conf'), vim $(find . -name '*.conf') – saginaw Dec 24 '15 at 21:04
  • @VladGURDIGA But I would like to know how to populate the arglist without quitting the current session as I have usually only one. I could also move around in the shell, search for a group of files and send them to a Vim server (with the argument --remote, see: vi.stackexchange.com/a/5618/4939) but I'm curious to know how to do it directly from the current session. If it's not possible that's fine, but after reading the help it seems it can be done. If it is, I would like to understand why vim reacts so differently to similar commands. – saginaw Dec 25 '15 at 2:43
  • Hey, it seems like in the first sample command you’re missing the closing backtick. ;) Aside from that though, I guess the example from the help use two backslashes instead of one to prevent shell expansion, as explained here: unix.stackexchange.com/q/104260/23669. – Vlad GURDIGA Dec 25 '15 at 12:19
  • In the case of find /etc -name '*.conf' not working, I’d think some funny names come out of that command, and, this may be the reason why it worked when piped through grep. – Vlad GURDIGA Dec 25 '15 at 12:26
6

I still don't know how to use backtick-expansion to populate the arglist with an arbitrary shell command, however I've found a workaround.

From :help `=:

You can have the backticks expanded as a Vim expression, instead of as an
external command, by putting an equal sign right after the first backtick,
e.g.:
    :e `=tempname()`
The expression can contain just about anything, thus this can also be used to
avoid the special meaning of '"', '|', '%' and '#'.  However, 'wildignore'
does apply like to other wildcards.

So, instead of expanding directly a shell command like this:

:args `shell command`

We can send the shell command to the Vim function systemlist() and use the expression register = to expand the resulting expression like this:

:args `=systemlist("shell command")`

To save some keystrokes, I've defined the following Ex command in my vimrc (:PA for Populate Arglist):

command! -nargs=1 PA args `=systemlist(<q-args>)`

And tested it with various shell commands:

:PA find . -name ver\*.c -print 2>/dev/null
:PA find . -name 'ver*.c' -print 2>/dev/null
:PA ls -t *.patch  | head -n1
:PA find /etc -name '*.conf' 2>/dev/null | grep input
:PA find /etc -name '*.conf' 2>/dev/null
:PA find . -name '*.conf' 2>/dev/null

So far it works like expected and the command can be typed as it would be in the shell (no need to escape the pipe for example).
It seems more consistent and reliable than direct backtick-expansion.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.