Is there a way to execute a host OS command directly, without spawning a shell that in turn runs the command? Preferably passing an argument list, in execvp() style? For example

" hypothetical
let finfo = systemexecvp('stat', fname)

Of course these days one has to add the disclaimer that this is not about interpolating variables in a shell command, not about the stat command or its portability, not about getting filesystem information etc.

  • Why do you need this? You could use job_start(). Of course you will have to take care of redirecting and all that stuff yourself then and getting the result back. Mar 14 at 8:28
  • Yes, it is job_start(), with all the documented caveats of course! As to why -- It just seems wasteful, other languages have exec()'s. Plus the $SHELL setting itself seems problematic (is it sh? the top-heavy bash? maybe fish? I actually have /bin/sh my Unix shell, but it immediately executes fish if present).
    – usretc
    Mar 14 at 8:37
  • 2
    Honestly, that sounds like pre-mature optimizing. I would rather concentrate or actual problems Mar 14 at 8:46
  • 1
    Please do not edit answers in to questions. Post an answer instead if you have a solution.
    – D. Ben Knoble
    Mar 14 at 21:06
  • 1
    I'll write an answer Mar 15 at 6:53

2 Answers 2


system(), as well as :!cmd always involves a shell. This can be verified by setting verbose mode :set verbose=5 and then running a command:

let a=system('true')
Calling shell to execute: "(true)>/tmp/vhEpKqI/0 2>&1"
Calling shell to execute: "true"

Typically Vim will use the users shell ($SHELL). But you can influence what shell to use and various other parameters. See the help for an extensive list of options that influence the behaviour of the shell.

If you want to bypass any shell and want to execute a command directly, you can use the channel feature together with the job_start() function. Note this is Vim specific, but I am sure Neovim has something similar.

Note, bypassing a shell means you will have to take care of redirection, buffering, pty-options, and much more options. See the extensive help at :h job-options.

And finally, if you want to run a command in the builtin terminal, without a shell, you can use the term_start() function.

  • Regarding the shell-related options: it's not clear how they might be manipulated safely in a plugin (running on someone else's machine). I suppose one can use setlocal and then restore them, probably in a try .. finally block. But shell commands can be invoked outside of a buffer context (e.g. during startup)... Anyway, thanks for your answer.
    – usretc
    Mar 15 at 16:51

I would do:

let finfo=system(printf('stat "%s"', fname))
  • I would read the text of the question, though, as system() DOES spawn a shell. Better add more text to the disclaimer... Oh, and that would probably be system('stat' .. shellescape(fname))
    – usretc
    Mar 14 at 8:01
  • How do you determine that a shell is spawn? In my tests I only have child process that is triggered. Mar 14 at 8:07
  • 1
    You are right. I have analyzed further and indeed a shell is spawn. On Windows it is even two shells that are spawn :-| Mar 14 at 8:14
  • 1
    it's actually logical that "someone" has to split the command around whitespaces; that someone is not vim, but the spawned shell. A lot of languages have execv commands, to complement their system() commands, which in turn rely on the execv* syscalls (with or without a fork())
    – usretc
    Mar 14 at 8:17
  • 1
    this is of course always calls a shell, as can be seen by :set verbose=5 or so Mar 14 at 8:29

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.