68

:!<command> can be used to execute a command in the shell. But this "takes over" my terminal and fills it with stdout of that particular command.

How do I execute a command in the background that only notifies me on a non-zero exit code?

  • 1
    Would you be willing to use the +python interface, or one of the other language interfaces? – joeytwiddle Feb 19 '15 at 11:17
  • 1
    @joeytwiddle Yes – OrangeTux Feb 19 '15 at 11:33
50

:silent exec "!command"

Note that your vim session will still be occupied while your command is executing. This is due to Vim's synchronous nature. You can still get back to your shell by pressing CTRL+z (to send Vim to the background) and then resume vim with the command fg as usual.

To do things asynchronously, take a look at Tim Pope's plugin vim-dispatch or the project NeoVim which has native support for asynchronous command execution, which you can easily leverage using the plugin NeoMake. The latest version of Vim also has support for asynchronous tasks.

See :h :silent

  • 5
    This is also the first thing that I tried when I saw the question :-) But if you use a command that outputs to stdout (or stderr), it will "clobber" your main Vim window (try :silent !ls) ... It also doesn't give proper output on a non-0 exit code... So I'm afraid it's a bit more involved than just using :silent... – Martin Tournoij Feb 19 '15 at 11:07
  • Oh, sorry. Added some notes about asynchronous execution while you wrote your comment. Don't know how to address the exit status issue. You might want to try in #vim on Freenode. – OliverUv Feb 19 '15 at 11:11
  • I also want to note that neither :silent exec "!ls" nor :silent !ls show any output at all on my version of Vim, 7.4 with patches 1-213. – OliverUv Feb 19 '15 at 11:14
  • 3
    Hm, here's what I get after :silent !ls: i.stack.imgur.com/1XvS6.png ... I need to press ^L to fix it again ... – Martin Tournoij Feb 19 '15 at 11:35
  • vim-dispatch certainly looks like a solution to the given problem. – joeytwiddle Feb 19 '15 at 11:56
29

To execute a command without triggering the Enter message, like:

Press ENTER or type command to continue

try the following simple example:

:silent !echo Hello

Then press Ctrl+L (or :redraw!) to refresh the screen when back to Vim.

To avoid need for refresh, you can define your own custom command, like:

:command! -nargs=1 Silent execute ':silent !'.<q-args> | execute ':redraw!'

Now, you can use the new Vim command to run a shell command (note: without ! and with capital S):

:Silent ps

Source: Avoiding the "Hit ENTER to continue" prompts at the Vim Wikia site

  • 1
    How do you get notified on a non-zero exit code? – Martin Tournoij Feb 25 '15 at 16:26
  • 1
    Not a solution to the non-zero exit code, but to add to this, this Silent command allows commands like :Silent ctags -R . > /dev/null & to run in the background. Sending stdout to /dev/null prevents output from appearing in the vim buffer. – ftvs May 29 '16 at 18:02
9

A quick and dirty solution (in pure Vimscript)

This can start a process in the background:

:!slow_command_here > /tmp/output 2>&1 &

But Vim needs a way to find out when the process has completed, and how, so let's use a marker file:

:!(rm -f /tmp/finished; slow_command_here > /tmp/output 2>&1; echo "$?" > /tmp/finished) &

Now we can ask Vim to check every so often whether the process has completed:

:augroup WaitForCompletion
:autocmd!
:autocmd CursorHold * if filereadable('/tmp/finished') | exec "augroup WaitForCompletion" | exec "au!" | exec "augroup END" | echo "Process completed with exit code ".readfile('/tmp/finished')[0] | end
:augroup END

Hey it's not pretty, but it sort-of works!

Disadvantages

Unfortunately the autocmd above won't trigger until you move the cursor. And if you wanted to run more than one background process at a time, you would need to add unique IDs to those files, and to the autocmd group name.

So if you can handle an extra dependency, you might be better off using a tried-and-tested solution like the vim-dispatch plugin mentioned in another answer.

Displaying output

If you want to see the output of the process, rather than just the exit code, then replace the final echo above with:

silent botright pedit /tmp/output

That would open the preview window. To use the quickfix error list instead:

silent cget /tmp/output | copen

The quickfix list lets you easily navigate to errors using :cnext. However copen moves your cursor into the quickfix window when it opens, which will probably be surprising/annoying.

(A workaround for that would be to open the QF window with :copen when initially starting the process, so you will not need to call it at the end.)

  • 2
    This is the only answer here that actually answers the question: " execute a command in the background that only notifies me on a non-zero exit code" ... I have no idea why the other answers even have upvotes at all... – Martin Tournoij Feb 25 '15 at 16:19
  • 2
    I believe they have upvotes because they perfectly answer the title of the question, which is what brings visitors to this page. "How to execute shell commands silently?" A common SE phenomenon! Visitors are grateful, but perhaps not disciplined. – joeytwiddle Apr 12 '16 at 2:43
  • In an ideal world, we would probably have two separate questions: "How to execute shell commands silently?" and "How to execute shell commands in the background?" so that googlers can find what they are looking for. – joeytwiddle Apr 12 '16 at 2:45
4

Running a command in the background

I was running a command that blocked for a bit, and I didn't really care about the output. This can be taken care of by starting the process attached not to the terminal/emulator but rather to the system and redirecting all output to /dev/null. For example:

:silent exec "!(espeak 'speaking in background'&) > /dev/null"

the (...&) runs it in the background and > /dev/null redirects all output to /dev/null (nothing).

The parenthesis trap the output into a subshell (or something like that), but it does have the side-effect of not being attached to the current shell (not a big deal).

Running a command silently in the background with mapping

I just realized that if you are, in fact, mapping it, you can do something like

nnoremap <silent> <leader>e :!$(espeak 'speaking in the background'&) > /dev/null

The above will display nothing in the command bar and will, additionally, not display anything in the terminal outside of vim. It will be mapped to <leader> e, which is \ e by default.

Running a command, capturing its output, displaying its contents

(Another edit - and maybe the neatest one). This one will display the output of a command if you so choose:


INSIDE A NEW TAB:

silent exec "!(echo 'hello. I'm a process! :)') > /tmp/vim_process" | :tabedit /tmp/vim_process

INSIDE A VERTICAL SPLIT:

silent exec "!(echo 'hello. I'm a process :)') > /tmp/vim_process" | :vs /tmp/vim_process

INSIDE A HORIZONTAL SPLIT:

silent exec "!(echo 'hello. I'm a process :)') > /tmp/vim_process" | :sp /tmp/vim_process

... do whatever you want

4

If you don't care about exit code, you can go with this:

:call system('/usr/bin/zathura using-docker.pdf &')
  • 1
    If you care about the exit code, the v:shell_error variable will tell you – Jerome Dalbert Mar 18 '18 at 22:22
3

Not sure if this suits your needs, (does not handle background processes as in foo & – not sure if that is what you mean by “in the background”), but a custom command could be used as in:

fun! s:PraeceptumTacet(cmd)
    silent let f = systemlist(a:cmd)
    if v:shell_error
        echohl Error
        echom "ERROR #" . v:shell_error
        echohl WarningMsg
        for e in f
            echom e
        endfor
        echohl None
    endif
endfun

command! -nargs=+ PT call s:PraeceptumTacet(<q-args>)

Then use as e.g.:

:PT ls -l
:PT foobar
ERROR #127
/bin/bash: foobar: command not found

If you do not need / want the error message a simple system() could suffice, then check for v:shell_error and report by e.g echo Error!.

From help v:shell_error:

                                        v:shell_error shell_error-variable
v:shell_error   Result of the last shell command.  When non-zero, the last
                shell command had an error.  When zero, there was no problem.
                This only works when the shell returns the error code to Vim.
                The value -1 is often used when the command could not be
                executed.  Read-only.
                Example: >
    :!mv foo bar
    :if v:shell_error
    :  echo 'could not rename "foo" to "bar"!'
    :endif
                "shell_error" also works, for backwards compatibility.
  • This isn't really running it in the background; you still need to wait for it to finish. – Martin Tournoij Feb 25 '15 at 16:26
  • @Carpetsmoker: Yes, thus my comment “… does not handle background processes as in foo & …”. From the Q text as a whole I interpreted it as being either or. Either “Execute as external command AKA background” or “Execute as external command _and_ as background process.”. I answered mainly based on the title of Q etc. – and added a comment on “Not sure …” – and left it to OP to clarify if either or. – Runium Feb 26 '15 at 20:30
2

Vim 8 introduced jobs support. One can run external command in the background without relying on plugins. For example, to run a markdown server (markserv) at current location and not block vim session:

:call job_start('markserv .')

This starts markserv process as a sub-process of current vim process. You can verify this with pstree.

See job_start

1

I use the following code:

command! -nargs=1 Silent call SilentExec(<q-args>)

function! SilentExec(cmd)
  let cmd = substitute(a:cmd, '^!', '', '')
  let cmd = substitute(cmd, '%', shellescape(expand('%')), '')
  call system(cmd)
endfunction

Now you can type the following

:Silent !your_command

This looks almost like Vim's built-in silent ! command, except for the capital S. It even allows for an optional ! to make it look even more similar. The call to system() makes the shell command truly silent: no screen flashes and no redraws.

(and if you ever need a status code, you can check the v:shell_error variable, see help for more info)

0

The AsyncRun plugin if designed for this, it allows you to run shell commands in the background in Vim8/NeoVim and display the output in the quickfix window.

Just as a replacement of ! command:

:AsyncRun ls -la /

You can also hide the output in quickfix window completely:

:AsyncRun -mode=3 ls -la /

When job finished, AsyncRun will notify you by passing an option -post:

:AsyncRun -post=some_vim_script   ls -la /

The vim script defined by -post will be executed after job finished.

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.