2

I've got a shell script: find... | stuff | xargs vi which works fine from the shell. But if I invoke that from inside Vim like :! find... | stuff | xargs vi it's kinda weird... like maybe I'm seeing different instance of vim that's spawned in a new shell? In that new vim :ls only shows the new buffer and after doing :q (from this mystery vim instance) then :ls shows the previous buffers available.

So do I want to use :e for this? I was reading several posts about :edit and :enew and such, but couldn't figure out how to combine that with this shell pipeline.


edit:
vim seems to be struggling with the shell globbing? and is it not able to accept piped commands through this syntax?

:e `find . -type f -not -path '*/\.*' | shuf -n 1`
E79: Cannot expand wildcards
"`find . -type f -not -path '*/.*'" [New DIRECTORY]
E492: Not an editor command: shuf -n 1`
Press ENTER or type command to continue

results in a buffer:

2 %a   "`find . -type f -not -path '*/.*'" line 1
3
  • What if you leave off the backslash on the . in -path '*/\.*'?
    – D. Ben Knoble
    Mar 23, 2022 at 13:13
  • doesn't your comment show an extra backslash that code does not show?
    – alec
    Mar 23, 2022 at 22:03
  • 1
    I'm suggesting instead of :args `find . -type f -not -path '*/\.*' | shuf -n 1` you try :args `find . -type f -not -path '*/.*' | shuf -n 1`, since I don't think that backslash is having any effect, but I could be wrong. It appears that the pipe was also part of the issue.
    – D. Ben Knoble
    Mar 24, 2022 at 14:55

3 Answers 3

2

You can use backquotes in an Ex command in Vim to use the output of a shell command as part of the Ex command arguments, similarly to how backquotes work on a Bourne shell.

Note that this seems to only work on Unix versions of Vim, not on Windows.

If you have a pipeline, you need to escape the pipes with a backslash and turn them into \|. The reason for that is that | is the command separator in Ex and Vim will first split the line into multiple Ex commands and then try to parse the backquotes, in which case an unescaped | will produce an unterminated backquoted shell command.

In your case, if your pipeline produces a single filename, you can use:

:e `find ... \| stuff`

If it produces more than one filename, then :e won't be able to take it (it only takes a single file), but in that case you can use :args to open all the files as "argument" files, which makes them work with :n to skip to the next file (exactly the same as passing filenames as arguments in the vi command-line.)

:args `find ... \| stuff`
5
  • 1
    This doesn't seem to work with my pipeline. I just edited my question to include the errors.
    – alec
    Mar 23, 2022 at 11:02
  • 1
    @alec Indeed! My local test was too simple and used a single command. If you have a pipe, you need to escape it, using \|. It's possible that you also need to escape your backslash (turning it into two). Hopefully that will fix it. I updated the answer to explain why it needs to be quoted and updated the examples too.
    – filbranden
    Mar 23, 2022 at 13:40
  • are there other special characters that need to be escaped the way | does in this context of using backticks to provide shell output as argument to ex command?
    – alec
    Mar 23, 2022 at 22:21
  • 1
    @alec I think it's hard to tell, since Vimscript escaping rules can be quite arcane... I guess probably backslash needs to be escaped (into two backslashes) and you might be able to escape a backquote itself with backslash backquote (though it's quite probable that one won't work at all.) There's also shell rules going into effect here, so it's quite a layered cake at that point... In any case, I imagine this was close to what you wanted, so hopefully it's useful. If you need more control, look into system() or systemlist() functions, that's more verbose but it can be better on escaping.
    – filbranden
    Mar 23, 2022 at 22:41
  • 1
    haha ""Vimscript escaping rules can be quite arcane"" exactly... that's why i asked :) thanks!
    – alec
    Mar 23, 2022 at 23:04
2

You can create a new command as such:

:command! -nargs=1 Find arglocal `=system("/usr/bin/find "..<q-args>)`

As the command does not have :h :command-bar attribute, the whole argument is passed to shell and the following works as expected:

:Find ... | stuff

All found files get into the local arglist and you can do as usual :next, :prev etc.

This also works in Windows provided that you have MSYS/bash and all the coretools installed properly.

0

Since in windows you can't just :e find ... \| stuff , you need something cleverer. And if you don't have cygwin, it is mostly prefered to use wsl.

func! AddFiles(grep)
   redir => tmp
   call RunPS('RunBash "' . a:grep . '"')
   redir END
   for l in split(tmp,'\n')
       if filereadable(l) 
           exec ':e '.l
        endif
    endfor
endfunc
func! RunPS(var)
        exec 'silent! !Import-Module ~\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1;' . a:var
endfunction 

RunBash is a powershell function that looks like this

function RunBash($fil)
{
wsl bash -c "source ~/.bash_profile; $fil" 
}

Sorry for the Blasphemy :)

For example,

    call AddFiles("find . -iname '*.py' | grep -v __init__")

1
  • It could be improved using Matt method maybe..
    – eyal karni
    Apr 30, 2022 at 9:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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