1. Introduction

Running shell commands from within third-party programs can be very convenient, as it provides many benefits without having to exit such applications:

  • embedded interpreting of shell language scripts
  • running external programs
  • controlling the operating system (OS)

Still, how this interaction is implemented determines the ways we can use it.

In this tutorial, we explore the many options users have to run shell commands within the Vi editor and suppress their output. First, we look at running shell commands within Vi. Next, we briefly touch on the subject of command output and session takeover suppression generally and in the editor. After that, we delve into several commands and constructs, which provide workarounds for silencing commands in Vi. Finally, we look at both native and plugin support for asynchronous job control.

For brevity, we use vi (Vi) when referencing both the Vi and Vim editors. Further, we use the [shell] and [Vi] markers to signify a shift to the respective environment.

We tested the code in this tutorial on Debian 11 (Bullseye) with GNU Bash 5.1.4. It should work in most POSIX-compliant environments.

2. Run Shell Commands in Vi

Indeed, the Vi editor provides a way to execute an external command directly from the interface. As usual, there are several ways to do so, each with its own specifics.

2.1. Background Vi

Since we mostly use shells to run the Vi editor, pressing the Ctrl+Z shortcut can activate the process backgrounding mechanics of our terminal. As a result, it suspends Vi and shows us the shell that called it:

$ fg

Critically, for most processes that we run inside Vi, the editor has to be in the foreground for them to execute.

2.2. Full-Fledged Shell

An easy way to spawn a shell from Vi is the :sh[ell] command:

$ exit

After executing the above, we have a child process of a shell. Exiting from it returns us to Vi. This method can be beneficial as a way to briefly switch to a shell and be able to come back to Vi. It can be roughly equivalent to placing the editor in the background.

2.3. Exclamation Point

Often, we want to run only a given set of shell commands and exit. Perhaps the simplest method for running one or more terminal commands and going back to Vi is the :! exclamation point command:

:!printf "Test 1.\n";printf "Test 2."
Test 1.
Test 2.
Press ENTER or type command to continue

Here, we use printf to output some text on the screen. Notice how the text itself appears after going from Vi to the shell. Pressing Return goes back to Vi.

There are convenient modifiers to the simple !:

  • :.!sh (shortcut: !!) sends the current line . as a command to sh, replacing it with the result in the buffer
  • :r! inserts the result of the shell command after ! in the buffer

These can be applied to other commands as well.

2.4. system()

By using the system() function in Vi, we can achieve flexible shell command execution:

:call system('printf "Test 1.\n";printf "Test 2."')

Here, we just :call the function and get no output. However, combined with :echo, we can see what our command outputs too.

Critically, :sh[ell], :!, and system() use many predefined settings.

2.5. Configuration

Of course, for any activity that involves the shell, Vi has to be aware of what the shell is. To that end, the editor stores it in the shell variable:

:set shell?

By default, the value of shell in Linux is either simply sh or the path to the current system shell as returned by $SHELL. Vi provides other shell settings as well.

In addition, when executing shell commands, we can check the read-only v:shell_error variable, which provides the return status when available.

3. Vi and Suppressing Shell Commands

There are many ways to run commands in the shell without any output or a session takeover. As usual, the main ones boil down to running a command in the background via job control, &, disown, nohup, or subshells, while redirecting standard streams.

To illustrate, let’s use sleep, followed by some output via printf:

$ { sleep 2 && printf 'Done.'; }

This block of code takes over the session for 2 seconds, after which we get a message.

Now, we can combine Vi’s external command execution with the usual shell output and session takeover suppression:

:!{ sleep 2 && printf 'Done.'; } >/dev/null 2>&1 &
[No write since last change]

Press ENTER or type command to continue

Here, we attempt to redirect all output to /dev/null while backgrounding (&) the process. Of course, we can choose any method for backgrounding, including detaching the process altogether.

Vi takes us to the shell, and we see no output from our command, as expected and desired. Still, we need to press Return to go back to the editor. This behavior is unlike executing the same directly in the shell:

$ { sleep 2 && printf 'Done.'; } >/dev/null 2>&1 &
[1] 666

In this case, the prompt reappears as soon as we submit the command, along with the PID of the background job. Thus, we can continue using the shell immediately without any further actions like pressing Return.

Let’s see how we can achieve this with Vi.

4. Complex Vi Shell Command Silencing with :silent!!

The compound :silent!! is another way to run shell commands in Vi. Effectively, we prepend silent! to a regular run with :!:

  • silent suppresses normal messages (similar to stdout)
  • ! suppresses error messages (similar to stderr)

Even with this prefix, we still see the shell screen while waiting for a command to complete. That’s why we’re going to use our example from the previous section, which backgrounds and silences:

:silent!!{ sleep 2 && printf 'Done.'; } >/dev/null 2>&1 &

In essence, we prevent output and session takeover in Vi via silent! while preventing the same in the shell via >/dev/null 2>&1 &.

Since Vi does go to shell mode and back, we might notice the screen flicker. After that, we’re back to the editor. However, there can be an issue with some screen contents disappearing. To remedy this, we press Ctrl+L or run the :redraw! command.

To avoid the last requirement, we can combine all of the steps into a new command:

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

Here’s the breakdown of this line:

  • :command! defines a new custom command
  • -nargs=1 ensures our new command takes only one argument
  • next comes the name of our custom command (:Silent)
  • execute runs :silent!! with our argument,  appending & to background
  • :redraw! is also (|) called to refresh the editor

After running the above or properly adding it to our Vi configuration file, we can use our custom :Silent with a shell command for quiet execution:

:Silent { sleep 2 && printf 'Done.'; } >/dev/null 2>&1

The screen flashes, and we’re back in the editor. Importantly, we lose the exit code, but we can choose to preserve the output by removing the redirections.

5. Easy Vi Quiet Command Run

Indeed, we already saw that the :call system() command does not provide any output and allows the session to continue uninterrupted (with &) even for long-running tasks:

:call system('{ sleep 2 && printf "Done."; } &')

In fact, this approach skips many of the hassles of using :silent!!. Similarly, we can make a standalone command:

:command! -nargs=1 Quiet execute ':call system('''.<q-args>.' &'')'

Now, we can simply use :Quiet:

:Quiet { sleep 2 && printf "Done."; }

One problem with this method is the fact that we can’t freely use single quotes within the argument to our command. Also, the & at the end might be a problem with more complex expressions.

6. Easier Vi Command Run with Prefixes

Indeed, there is a workaround for running a shell command quietly in Vi: using prefixes. Let’s take our earlier example:

:r!{ sleep 2 && printf 'Done.'; } >/dev/null 2>&1 &

Here, we apply the same mechanics as before for suppressing the output and running the task in the background. However, the :r prefix to ! ensures we don’t switch to and back from the shell.

Instead, our command runs in the background and, once finished, does not insert anything into the buffer because of the redirections.

7. Vi Native Support

Since Vi version 8, the editor has built-in jobs support. Basically, no plugins are needed to execute tasks in the background. For example, to run our example without interrupting the Vi session, we can again use call, but with job_start():

:call job_start('/bin/bash -c "{ sleep 2 && printf \"Done.\"; }"')

There are several peculiarities with this approach:

  • in Linux, execvp runs the job
  • we must specify which executable file to run (no default)
  • quoting can become a problem

To overcome the last issue at least partially, we can transform the call to one with square brackets:

:call job_start(['/bin/bash', '-c', '{ sleep 2 && printf "Done."; }'])

Now, we don’t need to escape double quotes.

Still, there is one major benefit to using this method as compared with the previous ones: here, we don’t need Vi in the foreground for the jobs to complete. Once a job is done, we can check its status via job_status().

Finally, the Vi jobs mechanism provides many options which control different aspects of a job run.

Similarly, we can use the job control of NeoVim via the jobstart() function, which supports asynchronous command execution natively.

8. External Plugins

In addition to the built-in capabilities, Vi also supports several plugins for asynchronous shell command runs. Since native support is usually more convenient, we briefly explore only one.

The AsyncRun plugin enables running shell commands in the background in both Vi and NeoVim. In fact, we can display the output in the quickfix window.

Conveniently, the usage of AsyncRun is mostly identical to the ! exclamation mark command:

:AsyncRun { sleep 2 && printf 'Done.'; } >/dev/null 2>&1

In addition, the plugin provides options to execute a script after our main command has finished. Further, we can completely hide the results with AsyncRun alone. Of course, the plugin is much more flexible.

9. Summary

In this article, we looked at ways to run shell commands and suppress their output while preventing session takeover within the Vi editor.

In conclusion, as usual for this ubiquitous application, there are numerous mechanisms to achieve the same result with different benefits and drawbacks.

Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.